From c85ebfd3c4ec9648d5aa15dd715a4308119a25ae Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Wed, 8 Nov 2023 23:29:35 -0500 Subject: [PATCH 01/13] Fix a bug with getting the latest milestone driver version --- seleniumbase/core/browser_launcher.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/seleniumbase/core/browser_launcher.py b/seleniumbase/core/browser_launcher.py index 969f8193..87a58e0d 100644 --- a/seleniumbase/core/browser_launcher.py +++ b/seleniumbase/core/browser_launcher.py @@ -2876,7 +2876,7 @@ def get_local_driver( disable_build_check = True uc_driver_version = None if is_using_uc(undetectable, browser_name): - if use_br_version_for_uc: + if use_br_version_for_uc or driver_version == "mlatest": uc_driver_version = get_uc_driver_version(full=True) full_ch_driver_version = uc_driver_version else: From 10fc029e7d1501bc4901c11f5319fc17c9e17ebb Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Wed, 8 Nov 2023 23:31:22 -0500 Subject: [PATCH 02/13] Add "replica" to --env=ENV options --- seleniumbase/fixtures/constants.py | 2 ++ seleniumbase/plugins/base_plugin.py | 1 + seleniumbase/plugins/db_reporting_plugin.py | 1 + seleniumbase/plugins/pytest_plugin.py | 1 + 4 files changed, 5 insertions(+) diff --git a/seleniumbase/fixtures/constants.py b/seleniumbase/fixtures/constants.py index ee25c7dd..dec042d8 100644 --- a/seleniumbase/fixtures/constants.py +++ b/seleniumbase/fixtures/constants.py @@ -10,6 +10,7 @@ class Environment: DEVELOP = "develop" PRODUCTION = "production" PERFORMANCE = "performance" + REPLICA = "replica" FEDRAMP = "fedramp" OFFLINE = "offline" ONLINE = "online" @@ -37,6 +38,7 @@ class ValidEnvs: "develop", "production", "performance", + "replica", "fedramp", "offline", "online", diff --git a/seleniumbase/plugins/base_plugin.py b/seleniumbase/plugins/base_plugin.py index f3c77b4e..4816d235 100644 --- a/seleniumbase/plugins/base_plugin.py +++ b/seleniumbase/plugins/base_plugin.py @@ -51,6 +51,7 @@ class Base(Plugin): constants.Environment.DEVELOP, constants.Environment.PRODUCTION, constants.Environment.PERFORMANCE, + constants.Environment.REPLICA, constants.Environment.FEDRAMP, constants.Environment.OFFLINE, constants.Environment.ONLINE, diff --git a/seleniumbase/plugins/db_reporting_plugin.py b/seleniumbase/plugins/db_reporting_plugin.py index 1e2a08d8..45b47b32 100644 --- a/seleniumbase/plugins/db_reporting_plugin.py +++ b/seleniumbase/plugins/db_reporting_plugin.py @@ -33,6 +33,7 @@ class DBReporting(Plugin): constants.Environment.DEVELOP, constants.Environment.PRODUCTION, constants.Environment.PERFORMANCE, + constants.Environment.REPLICA, constants.Environment.FEDRAMP, constants.Environment.OFFLINE, constants.Environment.ONLINE, diff --git a/seleniumbase/plugins/pytest_plugin.py b/seleniumbase/plugins/pytest_plugin.py index 430b0b45..3e3c8703 100644 --- a/seleniumbase/plugins/pytest_plugin.py +++ b/seleniumbase/plugins/pytest_plugin.py @@ -372,6 +372,7 @@ def pytest_addoption(parser): constants.Environment.DEVELOP, constants.Environment.PRODUCTION, constants.Environment.PERFORMANCE, + constants.Environment.REPLICA, constants.Environment.FEDRAMP, constants.Environment.OFFLINE, constants.Environment.ONLINE, From a2584476268974d99eaa66bcc53e7b5988c94114 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Wed, 8 Nov 2023 23:33:59 -0500 Subject: [PATCH 03/13] Refactor comments --- seleniumbase/plugins/driver_manager.py | 6 +++--- seleniumbase/plugins/sb_manager.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/seleniumbase/plugins/driver_manager.py b/seleniumbase/plugins/driver_manager.py index 1aa0f0f3..c4921c38 100644 --- a/seleniumbase/plugins/driver_manager.py +++ b/seleniumbase/plugins/driver_manager.py @@ -88,7 +88,7 @@ def Driver( undetectable=None, # Use undetected-chromedriver to evade bot-detection. uc_cdp_events=None, # Capture CDP events in undetected-chromedriver mode. uc_subprocess=None, # Use undetected-chromedriver as a subprocess. - log_cdp_events=None, # capture {"performance": "ALL", "browser": "ALL"}) + log_cdp_events=None, # Capture {"performance": "ALL", "browser": "ALL"} no_sandbox=None, # (DEPRECATED) - "--no-sandbox" is always used now. disable_gpu=None, # (DEPRECATED) - GPU is disabled if not "swiftshader". incognito=None, # Enable Chromium's Incognito mode. @@ -105,8 +105,8 @@ def Driver( firefox_arg=None, # "ARG=N,ARG2" (Set Firefox args, comma-separated.) firefox_pref=None, # SET (Set Firefox PREFERENCE:VALUE set, ","-separated) user_data_dir=None, # Set the Chrome user data directory to use. - extension_zip=None, # Load a Chrome Extension .zip|.crx, comma-separated.) - extension_dir=None, # Load a Chrome Extension directory, comma-separated.) + extension_zip=None, # Load a Chrome Extension .zip|.crx, comma-separated. + extension_dir=None, # Load a Chrome Extension directory, comma-separated. binary_location=None, # Set path of the Chromium browser binary to use. driver_version=None, # Set the chromedriver or uc_driver version to use. page_load_strategy=None, # Set Chrome PLS to "normal", "eager", or "none". diff --git a/seleniumbase/plugins/sb_manager.py b/seleniumbase/plugins/sb_manager.py index 3128c7e7..5ab3ba5f 100644 --- a/seleniumbase/plugins/sb_manager.py +++ b/seleniumbase/plugins/sb_manager.py @@ -54,7 +54,7 @@ def SB( undetectable=None, # Use undetected-chromedriver to evade bot-detection. uc_cdp_events=None, # Capture CDP events in undetected-chromedriver mode. uc_subprocess=None, # Use undetected-chromedriver as a subprocess. - log_cdp_events=None, # capture {"performance": "ALL", "browser": "ALL"}) + log_cdp_events=None, # Capture {"performance": "ALL", "browser": "ALL"} incognito=None, # Enable Chromium's Incognito mode. guest_mode=None, # Enable Chromium's Guest mode. dark_mode=None, # Enable Chromium's Dark mode. From 089454324743e7552cb72a66eb9104c81abb9ee8 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Wed, 8 Nov 2023 23:35:49 -0500 Subject: [PATCH 04/13] Update timing --- seleniumbase/fixtures/base_case.py | 23 ++++++++++++++++++----- seleniumbase/fixtures/js_utils.py | 7 ++++--- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/seleniumbase/fixtures/base_case.py b/seleniumbase/fixtures/base_case.py index ee45a560..934fda14 100644 --- a/seleniumbase/fixtures/base_case.py +++ b/seleniumbase/fixtures/base_case.py @@ -625,22 +625,22 @@ class BaseCase(unittest.TestCase): except Exception: pass if self.__needs_minimum_wait() or self.browser == "safari": - time.sleep(0.04) + time.sleep(0.045) try: if self.driver.current_url != pre_action_url: self.__ad_block_as_needed() self.__disable_beforeunload_as_needed() if self.__needs_minimum_wait(): - time.sleep(0.06) + time.sleep(0.075) except Exception: try: self.wait_for_ready_state_complete() except Exception: pass if self.__needs_minimum_wait(): - time.sleep(0.04) + time.sleep(0.05) else: - time.sleep(0.08) + time.sleep(0.085) if self.demo_mode: if self.driver.current_url != pre_action_url: if not js_utils.is_jquery_activated(self.driver): @@ -900,7 +900,7 @@ class BaseCase(unittest.TestCase): if not self.demo_mode and not self.slow_mode: self.__scroll_to_element(element, selector, by) if self.__needs_minimum_wait(): - time.sleep(0.02) + time.sleep(0.04) try: element.clear() # May need https://stackoverflow.com/a/50691625 backspaces = Keys.BACK_SPACE * 42 # Is the answer to everything @@ -1580,6 +1580,9 @@ class BaseCase(unittest.TestCase): return if not self.is_link_text_present(link_text): self.wait_for_link_text_present(link_text, timeout=timeout) + if not self.demo_mode and not self.slow_mode: + if self.__needs_minimum_wait(): + time.sleep(0.04) pre_action_url = None try: pre_action_url = self.driver.current_url @@ -1668,6 +1671,8 @@ class BaseCase(unittest.TestCase): self.__demo_mode_pause_if_active(tiny=True) elif self.slow_mode: self.__slow_mode_pause_if_active() + elif self.__needs_minimum_wait(): + time.sleep(0.04) def click_partial_link_text(self, partial_link_text, timeout=None): """This method clicks the partial link text on a page.""" @@ -7827,6 +7832,11 @@ class BaseCase(unittest.TestCase): self.__demo_mode_highlight_if_active(original_selector, by) if scroll and not self.demo_mode and not self.slow_mode: self.scroll_to(original_selector, by=by, timeout=timeout) + if self.__needs_minimum_wait(): + time.sleep(0.04) + if not scroll and not self.demo_mode and not self.slow_mode: + if self.__needs_minimum_wait(): + time.sleep(0.06) text = self.__get_type_checked_text(text) value = re.escape(text) value = self.__escape_quotes_if_needed(value) @@ -7895,6 +7905,9 @@ class BaseCase(unittest.TestCase): except Exception: pass self.__demo_mode_pause_if_active() + if not self.demo_mode and not self.slow_mode: + if self.__needs_minimum_wait(): + time.sleep(0.04) def js_update_text(self, selector, text, by="css selector", timeout=None): """JavaScript + send_keys are used to update a text field. diff --git a/seleniumbase/fixtures/js_utils.py b/seleniumbase/fixtures/js_utils.py index e83157d2..86dd8087 100644 --- a/seleniumbase/fixtures/js_utils.py +++ b/seleniumbase/fixtures/js_utils.py @@ -863,14 +863,15 @@ def set_messenger_theme( try: driver.execute_script(msg_style) except Exception: - time.sleep(0.05) + time.sleep(0.03) activate_messenger(driver) - time.sleep(0.05) + time.sleep(0.15) try: driver.execute_script(msg_style) + time.sleep(0.02) except Exception: pass - time.sleep(0.1) + time.sleep(0.05) def post_message(driver, message, msg_dur=None, style="info"): From 7dd35c58569e4296313c12eeea02e2c91f791f70 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Wed, 8 Nov 2023 23:37:52 -0500 Subject: [PATCH 05/13] Fix a bug with "invalid session id" --- seleniumbase/fixtures/base_case.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/seleniumbase/fixtures/base_case.py b/seleniumbase/fixtures/base_case.py index 934fda14..70a2f00c 100644 --- a/seleniumbase/fixtures/base_case.py +++ b/seleniumbase/fixtures/base_case.py @@ -315,6 +315,10 @@ class BaseCase(unittest.TestCase): self.driver.get(url) else: pass # Odd issue where the open did happen. Continue. + elif "invalid session id" in e.msg: + logging.debug("Invalid session id. Will open new browser.") + self.driver = self.get_new_driver() + self.driver.get(url) else: raise if ( From f28c1b1e7ce1a6b2a5045e1ec3fe63db20dee57e Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Wed, 8 Nov 2023 23:40:10 -0500 Subject: [PATCH 06/13] Improve error-handling with UC Mode --- seleniumbase/undetected/__init__.py | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/seleniumbase/undetected/__init__.py b/seleniumbase/undetected/__init__.py index 8a8a35b1..3fbb1a57 100644 --- a/seleniumbase/undetected/__init__.py +++ b/seleniumbase/undetected/__init__.py @@ -330,18 +330,23 @@ class Chrome(selenium.webdriver.chrome.webdriver.WebDriver): return object.__dir__(self) def _get_cdc_props(self): - return self.execute_script( - """ - let objectToInspect = window, - result = []; - while(objectToInspect !== null) - { result = result.concat( - Object.getOwnPropertyNames(objectToInspect) - ); - objectToInspect = Object.getPrototypeOf(objectToInspect); } - return result.filter(i => i.match(/^[a-z]{3}_[a-z]{22}_.*/i)) - """ - ) + cdc_props = [] + try: + cdc_props = self.execute_script( + """ + let objectToInspect = window, + result = []; + while(objectToInspect !== null) + { result = result.concat( + Object.getOwnPropertyNames(objectToInspect) + ); + objectToInspect = Object.getPrototypeOf(objectToInspect); } + return result.filter(i => i.match(/^[a-z]{3}_[a-z]{22}_.*/i)) + """ + ) + except Exception: + pass + return cdc_props def _hook_remove_cdc_props(self, cdc_props): if len(cdc_props) < 1: From 25f712ca1ac0138182eba450948f0b160a28d917 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Wed, 8 Nov 2023 23:44:05 -0500 Subject: [PATCH 07/13] Update the algorithm that decides if UC Mode calls reconnect() --- seleniumbase/core/browser_launcher.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/seleniumbase/core/browser_launcher.py b/seleniumbase/core/browser_launcher.py index 87a58e0d..610f8350 100644 --- a/seleniumbase/core/browser_launcher.py +++ b/seleniumbase/core/browser_launcher.py @@ -308,14 +308,18 @@ def find_edgedriver_version_to_use(use_version, driver_version): def has_cf(text): if ( - "Just a moment..." in text - or "403 Forbidden" in text + "403 Forbidden" in text or "Permission Denied" in text or 'id="challenge-error-text"' in text + or "Just a moment..." in text or 'action="/?__cf_chl_f_tk' in text or 'src="chromedriver.js"' in text + or 'class="g-recaptcha"' in text + or 'content="Pixelscan"' in text or 'id="challenge-form"' in text or "window._cf_chl_opt" in text + or "/recaptcha/api.js" in text + or "/turnstile/" in text ): return True return False From 0695e51712c139856648fbf6854b9177233dc64f Mon Sep 17 00:00:00 2001 From: Michael Mintz <mdmintz@gmail.com> Date: Wed, 8 Nov 2023 23:48:27 -0500 Subject: [PATCH 08/13] Make improvements to "driver.uc_click(selector)" (UC Mode only) --- seleniumbase/core/browser_launcher.py | 17 +++++++++++++++-- seleniumbase/undetected/webelement.py | 27 +++++++++++++++++++-------- 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/seleniumbase/core/browser_launcher.py b/seleniumbase/core/browser_launcher.py index 610f8350..6081ddf6 100644 --- a/seleniumbase/core/browser_launcher.py +++ b/seleniumbase/core/browser_launcher.py @@ -431,11 +431,24 @@ def uc_open_with_reconnect(driver, url, reconnect_time=None): def uc_click( - driver, selector, by="css selector", timeout=settings.SMALL_TIMEOUT + driver, + selector, + by="css selector", + timeout=settings.SMALL_TIMEOUT, + reconnect_time=None, ): + try: + rct = float(by) # Add shortcut: driver.uc_click(selector, RCT) + if not reconnect_time: + reconnect_time = rct + by = "css selector" + except Exception: + pass element = driver.wait_for_element(selector, by=by, timeout=timeout) try: - element.uc_click() + element.uc_click( + driver, selector, by=by, reconnect_time=reconnect_time + ) except ElementClickInterceptedException: driver.js_click(selector, by=by, timeout=timeout) diff --git a/seleniumbase/undetected/webelement.py b/seleniumbase/undetected/webelement.py index 673c7b69..17e3a05c 100644 --- a/seleniumbase/undetected/webelement.py +++ b/seleniumbase/undetected/webelement.py @@ -1,14 +1,25 @@ import selenium.webdriver.remote.webelement -from seleniumbase.config import settings class WebElement(selenium.webdriver.remote.webelement.WebElement): - def uc_click(self): - super().click() - if ( - hasattr(settings, "PAGE_LOAD_STRATEGY") - and settings.PAGE_LOAD_STRATEGY == "none" - ): - pass + def uc_click( + self, + driver=None, + selector=None, + by=None, + reconnect_time=None, + ): + if driver and selector and by: + driver.js_click(selector, by=by, timeout=1) else: + super().click() + if not reconnect_time: self._parent.reconnect(0.1) + else: + self._parent.reconnect(reconnect_time) + + def uc_reconnect(self, reconnect_time=None): + if not reconnect_time: + self._parent.reconnect(0.1) + else: + self._parent.reconnect(reconnect_time) From 2efd21c0dc36d05ebd059ee0eb2b4199e65dca20 Mon Sep 17 00:00:00 2001 From: Michael Mintz <mdmintz@gmail.com> Date: Wed, 8 Nov 2023 23:50:32 -0500 Subject: [PATCH 09/13] Add a customizable "timeout" arg to "highlight()" methods --- help_docs/method_summary.md | 9 ++++--- seleniumbase/fixtures/base_case.py | 42 ++++++++++++++++++++++++++---- 2 files changed, 42 insertions(+), 9 deletions(-) diff --git a/help_docs/method_summary.md b/help_docs/method_summary.md index 06b1f7ce..c75648bf 100644 --- a/help_docs/method_summary.md +++ b/help_docs/method_summary.md @@ -363,13 +363,14 @@ self.bring_active_window_to_front() self.bring_to_front(selector, by="css selector") -self.highlight_click(selector, by="css selector", loops=3, scroll=True) +self.highlight_click(selector, by="css selector", loops=3, scroll=True, timeout=None) -self.highlight_type(selector, text, by="css selector", loops=3, scroll=True) +self.highlight_type(selector, text, by="css selector", loops=3, scroll=True, timeout=None) # Duplicates: -# self.highlight_update_text(selector, text, by="css selector", loops=3, scroll=True) +# self.highlight_update_text( +# selector, text, by="css selector", loops=3, scroll=True, timeout=None) -self.highlight(selector, by="css selector", loops=4, scroll=True) +self.highlight(selector, by="css selector", loops=4, scroll=True, timeout=None) self.press_up_arrow(selector="html", times=1, by="css selector") diff --git a/seleniumbase/fixtures/base_case.py b/seleniumbase/fixtures/base_case.py index 70a2f00c..4195428d 100644 --- a/seleniumbase/fixtures/base_case.py +++ b/seleniumbase/fixtures/base_case.py @@ -5508,29 +5508,50 @@ class BaseCase(unittest.TestCase): self.execute_script(script) def highlight_click( - self, selector, by="css selector", loops=3, scroll=True + self, selector, by="css selector", loops=3, scroll=True, timeout=None, ): """Highlights the element and then clicks it.""" self.__check_scope() + if not timeout: + timeout = settings.SMALL_TIMEOUT + self.wait_for_element_visible(selector, by=by, timeout=timeout) if not self.demo_mode: self.__highlight(selector, by=by, loops=loops, scroll=scroll) self.click(selector, by=by) def highlight_update_text( - self, selector, text, by="css selector", loops=3, scroll=True + self, + selector, + text, + by="css selector", + loops=3, + scroll=True, + timeout=None, ): """Highlights the element and then types text into the field.""" self.__check_scope() + if not timeout: + timeout = settings.SMALL_TIMEOUT + self.wait_for_element_visible(selector, by=by, timeout=timeout) if not self.demo_mode: self.__highlight(selector, by=by, loops=loops, scroll=scroll) self.update_text(selector, text, by=by) def highlight_type( - self, selector, text, by="css selector", loops=3, scroll=True + self, + selector, + text, + by="css selector", + loops=3, + scroll=True, + timeout=None, ): """Same as self.highlight_update_text() As above, highlights the element and then types text into the field.""" self.__check_scope() + if not timeout: + timeout = settings.SMALL_TIMEOUT + self.wait_for_element_visible(selector, by=by, timeout=timeout) if not self.demo_mode: self.__highlight(selector, by=by, loops=loops, scroll=scroll) self.update_text(selector, text, by=by) @@ -5613,15 +5634,26 @@ class BaseCase(unittest.TestCase): pass # JQuery probably couldn't load. Skip highlighting. time.sleep(0.065) - def highlight(self, selector, by="css selector", loops=None, scroll=True): + def highlight( + self, + selector, + by="css selector", + loops=None, + scroll=True, + timeout=None, + ): """This method uses fancy JavaScript to highlight an element. @Params selector - the selector of the element to find by - the type of selector to search by (Default: CSS) loops - # of times to repeat the highlight animation (Default: 4. Each loop lasts for about 0.2s) - scroll - the option to scroll to the element first (Default: True) """ + scroll - the option to scroll to the element first (Default: True) + timeout - the time to wait for the element to appear """ self.__check_scope() + if not timeout: + timeout = settings.SMALL_TIMEOUT + self.wait_for_element_visible(selector, by=by, timeout=timeout) self.__highlight(selector=selector, by=by, loops=loops, scroll=scroll) if self.recorder_mode and self.__current_url_is_recordable(): if self.get_session_storage_item("pause_recorder") == "no": From 8ecee571458a5f33303ab9dbb31907c0c084b065 Mon Sep 17 00:00:00 2001 From: Michael Mintz <mdmintz@gmail.com> Date: Wed, 8 Nov 2023 23:54:42 -0500 Subject: [PATCH 10/13] Update the documentation --- examples/translations/ReadMe.md | 2 ++ help_docs/method_summary.md | 1 + help_docs/syntax_formats.md | 4 ++++ help_docs/translations.md | 2 ++ mkdocs.yml | 5 +++++ mkdocs_build/requirements.txt | 2 +- 6 files changed, 15 insertions(+), 1 deletion(-) diff --git a/examples/translations/ReadMe.md b/examples/translations/ReadMe.md index 052fa0f4..4bec061c 100644 --- a/examples/translations/ReadMe.md +++ b/examples/translations/ReadMe.md @@ -15,6 +15,8 @@ class 我的测试类(硒测试用例): self.开启("https://zh.wikipedia.org/wiki/") self.断言标题("维基百科,自由的百科全书") self.断言元素('a[title="Wikipedia:关于"]') + self.断言元素('span:contains("创建账号")') + self.断言元素('span:contains("登录")') self.断言文本("新闻动态", "span#新闻动态") self.输入文本('input[name="search"]', "舞龍") self.单击('button:contains("搜索")') diff --git a/help_docs/method_summary.md b/help_docs/method_summary.md index c75648bf..7dca4ce5 100644 --- a/help_docs/method_summary.md +++ b/help_docs/method_summary.md @@ -1026,6 +1026,7 @@ driver.uc_click(selector) * [test_login.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_login.py) * [test_markers.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_markers.py) * [test_swag_labs.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_swag_labs.py) +* [test_simple_login.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_simple_login.py) * [test_suite.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_suite.py) * [test_tinymce.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_tinymce.py) * And many more... diff --git a/help_docs/syntax_formats.md b/help_docs/syntax_formats.md index ea04283d..d0393564 100644 --- a/help_docs/syntax_formats.md +++ b/help_docs/syntax_formats.md @@ -450,6 +450,8 @@ class 我的测试类(硒测试用例): self.开启("https://zh.wikipedia.org/wiki/") self.断言标题("维基百科,自由的百科全书") self.断言元素('a[title="Wikipedia:关于"]') + self.断言元素('span:contains("创建账号")') + self.断言元素('span:contains("登录")') self.断言文本("新闻动态", "span#新闻动态") self.输入文本('input[name="search"]', "舞龍") self.单击('button:contains("搜索")') @@ -973,6 +975,8 @@ finally: The ``Driver()`` manager format can be used as a drop-in replacement for virtually every Python/selenium framework, as it uses the raw ``driver`` instance for handling commands. The ``Driver()`` method simplifies the work of managing drivers with optimal settings, and it can be configured with multiple args. The ``Driver()`` also accepts command-line options (such as ``python --headless``) so that you don't need to modify your tests directly to use different settings. These command-line options only take effect if the associated method args remain unset (or set to ``None``) for the specified options. +When using the ``Driver()`` format, you may need to activate a Virtual Display on your own if you want to run headed tests in a headless Linux environment. (See https://github.com/mdmintz/sbVirtualDisplay for details.) One such example of this is using an authenticated proxy, which is configured via a Chrome extension that is generated at runtime. (Note that regular headless mode in Chrome doesn't support extensions.) + -------- <h3 align="left"><a href="https://github.com/seleniumbase/SeleniumBase/"><img src="https://seleniumbase.github.io/img/sb_logo_10.png" title="SeleniumBase" width="280" /></a></h3> diff --git a/help_docs/translations.md b/help_docs/translations.md index 69747301..3f292d7a 100644 --- a/help_docs/translations.md +++ b/help_docs/translations.md @@ -17,6 +17,8 @@ class 我的测试类(硒测试用例): self.开启("https://zh.wikipedia.org/wiki/") self.断言标题("维基百科,自由的百科全书") self.断言元素('a[title="Wikipedia:关于"]') + self.断言元素('span:contains("创建账号")') + self.断言元素('span:contains("登录")') self.断言文本("新闻动态", "span#新闻动态") self.输入文本('input[name="search"]', "舞龍") self.单击('button:contains("搜索")') diff --git a/mkdocs.yml b/mkdocs.yml index a0dc3236..580dc7c7 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -152,6 +152,11 @@ nav: - ↔️ W3Schools drag & drop: https://seleniumbase.io/w3schools/drag_drop - ☑️ W3Schools checkboxes: https://seleniumbase.io/w3schools/checkboxes - 🔘 W3Schools radio buttons: https://seleniumbase.io/w3schools/radio_buttons + - Pages with CAPTCHAs: + - 🔑 CF Turnstile Test: https://seleniumbase.io/apps/turnstile + - 🔑 CF Turnstile on Form: https://seleniumbase.io/apps/form_turnstile + - 🔐 reCAPTCHA v2 Test: https://seleniumbase.io/apps/recaptcha + - 🔐 reCAPTCHA v2 on Form: https://seleniumbase.io/apps/form_recaptcha - Additional Help Docs: - 📑 Table of Contents: help_docs/ReadMe.md - 🖼️ How to handle iframes: help_docs/handling_iframes.md diff --git a/mkdocs_build/requirements.txt b/mkdocs_build/requirements.txt index b60a226a..5a0b0844 100644 --- a/mkdocs_build/requirements.txt +++ b/mkdocs_build/requirements.txt @@ -20,7 +20,7 @@ paginate==0.5.6 pyquery==2.0.0 readtime==3.0.0 mkdocs==1.5.3 -mkdocs-material==9.4.7 +mkdocs-material==9.4.8 mkdocs-exclude-search==0.6.5 mkdocs-simple-hooks==0.1.5 mkdocs-material-extensions==1.3 From 655c5c108ac6f8ab8e52f04b59b81fa3a14b8562 Mon Sep 17 00:00:00 2001 From: Michael Mintz <mdmintz@gmail.com> Date: Thu, 9 Nov 2023 00:06:51 -0500 Subject: [PATCH 11/13] Update example tests --- examples/boilerplates/samples/google_test.py | 10 ++++- .../boilerplates/samples/test_page_objects.py | 4 ++ examples/locale_code_test.py | 19 +++++++--- examples/raw_form_turnstile.py | 37 +++++++++++++++++++ examples/raw_turnstile.py | 16 ++++++++ examples/test_cdp_ad_blocking.py | 28 ++++++++++++++ examples/test_repeat_tests.py | 1 + examples/translations/chinese_test_1.py | 2 + 8 files changed, 111 insertions(+), 6 deletions(-) create mode 100644 examples/raw_form_turnstile.py create mode 100644 examples/raw_turnstile.py create mode 100644 examples/test_cdp_ad_blocking.py diff --git a/examples/boilerplates/samples/google_test.py b/examples/boilerplates/samples/google_test.py index bd9b1376..7164c386 100644 --- a/examples/boilerplates/samples/google_test.py +++ b/examples/boilerplates/samples/google_test.py @@ -1,10 +1,18 @@ """google.com example test that uses page objects""" from seleniumbase import BaseCase -from .google_objects import HomePage, ResultsPage +try: + from .google_objects import HomePage, ResultsPage +except Exception: + from google_objects import HomePage, ResultsPage + BaseCase.main(__name__, __file__) class GoogleTests(BaseCase): def test_google_dot_com(self): + if self.headless and self._multithreaded: + self.open_if_not_url("about:blank") + print("Skipping test in headless multi-threaded mode.") + self.skip("Skipping test in headless multi-threaded mode.") self.open("https://google.com/ncr") self.assert_title_contains("Google") self.sleep(0.05) diff --git a/examples/boilerplates/samples/test_page_objects.py b/examples/boilerplates/samples/test_page_objects.py index d4421ec3..07e0a99b 100644 --- a/examples/boilerplates/samples/test_page_objects.py +++ b/examples/boilerplates/samples/test_page_objects.py @@ -33,6 +33,10 @@ class SeleniumBaseIOPage: class MyTests(BaseCase): def test_page_objects(self): + if self.headless and self._multithreaded: + self.open_if_not_url("about:blank") + print("Skipping test in headless multi-threaded mode.") + self.skip("Skipping test in headless multi-threaded mode.") search_term = "SeleniumBase.io Docs" expected_text = "SeleniumBase" GooglePage().go_to_google(self) diff --git a/examples/locale_code_test.py b/examples/locale_code_test.py index bc6fb18d..64d6fc6f 100644 --- a/examples/locale_code_test.py +++ b/examples/locale_code_test.py @@ -4,9 +4,18 @@ BaseCase.main(__name__, __file__) class LocaleCodeTests(BaseCase): def test_locale_code(self): - self.open("https://localeplanet.com/support/browser.html") - locale_code = self.get_locale_code() + self.open("about:blank") + locale_code = self.get_locale_code() # navigator.language print("\nYour Browser's Locale Code: %s" % locale_code) - expected_text = "navigator.language: %s" % locale_code - self.demo_mode = True # Display test actions - self.assert_text(expected_text, "pre") + if self.browser == "chrome" and not self.headless: + self.open("chrome://settings/languages") + language_info = self.get_text( + "settings-ui::shadow " + "settings-main::shadow " + "settings-basic-page::shadow " + "settings-languages-page::shadow " + "#languagesSection div.start div" + ) + print("Language info (chrome://settings/languages):") + print(language_info) + self.sleep(1) diff --git a/examples/raw_form_turnstile.py b/examples/raw_form_turnstile.py new file mode 100644 index 00000000..14e6a7a5 --- /dev/null +++ b/examples/raw_form_turnstile.py @@ -0,0 +1,37 @@ +from seleniumbase import SB + + +def click_turnstile_and_verify(sb): + sb.driver.reconnect(0.1) + iframe = sb.driver.find_element("iframe") + sb.driver.reconnect(0.5) + sb.driver.switch_to.frame(iframe) + sb.driver.uc_click("span.mark") + sb.highlight("img#captcha-success", timeout=3.33) + + +with SB(uc=True, test=True) as sb: + sb.driver.uc_open_with_reconnect( + "https://seleniumbase.io/apps/form_turnstile", + reconnect_time=2.33, + ) + try: + click_turnstile_and_verify(sb) + except Exception: + sb.driver.uc_open_with_reconnect( + "https://seleniumbase.io/apps/form_turnstile", + reconnect_time=2.33, + ) + click_turnstile_and_verify(sb) + sb.press_keys("#name", "SeleniumBase") + sb.press_keys("#email", "test@test.test") + sb.press_keys("#phone", "1-555-555-5555") + sb.click('[for="date"]') + sb.click("td.is-today button") + sb.click('div[class="select-wrapper"] input') + sb.click('span:contains("9:00 PM")') + sb.highlight_click('input[value="AR"] + span') + sb.click('input[value="cc"] + span') + sb.highlight_click('button:contains("Request & Pay")') + sb.highlight("img#submit-success") + sb.highlight('button:contains("Success!")') diff --git a/examples/raw_turnstile.py b/examples/raw_turnstile.py new file mode 100644 index 00000000..8a8da675 --- /dev/null +++ b/examples/raw_turnstile.py @@ -0,0 +1,16 @@ +from seleniumbase import SB + +with SB(uc=True, test=True) as sb: + sb.driver.uc_open_with_reconnect( + "https://seleniumbase.io/apps/turnstile", + reconnect_time=2.33, + ) + sb.driver.reconnect(0.1) + iframe = sb.driver.find_element("iframe") + sb.driver.reconnect(0.5) + sb.driver.switch_to.frame(iframe) + sb.driver.uc_click("span.mark") + sb.switch_to_default_content() + sb.assert_element("img#captcha-success", timeout=3.33) + sb.set_messenger_theme(location="top_left") + sb.post_message("Selenium wasn't detected!", duration=3) diff --git a/examples/test_cdp_ad_blocking.py b/examples/test_cdp_ad_blocking.py new file mode 100644 index 00000000..285781c6 --- /dev/null +++ b/examples/test_cdp_ad_blocking.py @@ -0,0 +1,28 @@ +from seleniumbase import BaseCase +BaseCase.main(__name__, __file__) + + +class CDPNetworkBlockingTests(BaseCase): + def test_cdp_network_blocking(self): + if not self.is_chromium: + self.skip("This test is only for Chromium browsers!") + self.execute_cdp_cmd( + 'Network.setBlockedURLs', {"urls": [ + "*googlesyndication.com*", + "*doubleclick.net*", + "*adsafeprotected.com*", + "*2mdn.net*", + "*googletagmanager.com*", + "*adsafeprotected.com*", + "*snigelweb.com*", + "*fastclick.net*", + "*amazon-adsystem.com*", + "*google-analytics.com*", + ]}) + self.execute_cdp_cmd('Network.enable', {}) + self.open('https://www.w3schools.com/jquery/default.asp') + source = self.get_page_source() + self.assert_true("doubleclick.net" not in source) + self.assert_true("google-analytics.com" not in source) + if self.demo_mode: + self.post_message("Blocking was successful!") diff --git a/examples/test_repeat_tests.py b/examples/test_repeat_tests.py index 6c696a29..7fb50f8a 100644 --- a/examples/test_repeat_tests.py +++ b/examples/test_repeat_tests.py @@ -5,6 +5,7 @@ import pytest from parameterized import parameterized from seleniumbase import BaseCase +BaseCase.main(__name__, __file__, "-n6") url = "data:text/html,<h2>Hello</h2><p><input /> <button>OK!</button></p>" diff --git a/examples/translations/chinese_test_1.py b/examples/translations/chinese_test_1.py index 7b417ce3..4d899f98 100644 --- a/examples/translations/chinese_test_1.py +++ b/examples/translations/chinese_test_1.py @@ -8,6 +8,8 @@ class 我的测试类(硒测试用例): self.开启("https://zh.wikipedia.org/wiki/") self.断言标题("维基百科,自由的百科全书") self.断言元素('a[title="Wikipedia:关于"]') + self.断言元素('span:contains("创建账号")') + self.断言元素('span:contains("登录")') self.断言文本("新闻动态", "span#新闻动态") self.输入文本('input[name="search"]', "舞龍") self.单击('button:contains("搜索")') From c76c5a15ac0c208010da19c39bc2b2a8ccf931ab Mon Sep 17 00:00:00 2001 From: Michael Mintz <mdmintz@gmail.com> Date: Thu, 9 Nov 2023 00:07:49 -0500 Subject: [PATCH 12/13] Refresh Python dependencies --- requirements.txt | 7 ++++--- setup.py | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/requirements.txt b/requirements.txt index 076012bd..fa668b9f 100755 --- a/requirements.txt +++ b/requirements.txt @@ -21,11 +21,12 @@ pynose==1.4.8 sniffio==1.3.0 h11==0.14.0 outcome==1.3.0.post0 -trio==0.22.2 +trio==0.22.2;python_version<"3.8" +trio==0.23.1;python_version>="3.8" trio-websocket==0.11.1 wsproto==1.2.0 selenium==4.11.2;python_version<"3.8" -selenium==4.14.0;python_version>="3.8" +selenium==4.15.2;python_version>="3.8" cssselect==1.2.0 sortedcontainers==2.4.0 fasteners==0.19 @@ -41,7 +42,7 @@ pytest-ordering==0.6 pytest-rerunfailures==12.0 pytest-xdist==3.3.1 parameterized==0.9.0 -sbvirtualdisplay==1.2.0 +sbvirtualdisplay==1.3.0 behave==1.2.6 soupsieve==2.4.1;python_version<"3.8" soupsieve==2.5;python_version>="3.8" diff --git a/setup.py b/setup.py index e0802d5c..a3b3bc8b 100755 --- a/setup.py +++ b/setup.py @@ -154,11 +154,12 @@ setup( 'sniffio==1.3.0', 'h11==0.14.0', 'outcome==1.3.0.post0', - 'trio==0.22.2', + 'trio==0.22.2;python_version<"3.8"', + 'trio==0.23.1;python_version>="3.8"', 'trio-websocket==0.11.1', 'wsproto==1.2.0', 'selenium==4.11.2;python_version<"3.8"', - 'selenium==4.14.0;python_version>="3.8"', + 'selenium==4.15.2;python_version>="3.8"', 'cssselect==1.2.0', "sortedcontainers==2.4.0", 'fasteners==0.19', @@ -174,7 +175,7 @@ setup( 'pytest-rerunfailures==12.0', 'pytest-xdist==3.3.1', 'parameterized==0.9.0', - "sbvirtualdisplay==1.2.0", + "sbvirtualdisplay==1.3.0", "behave==1.2.6", 'soupsieve==2.4.1;python_version<"3.8"', 'soupsieve==2.5;python_version>="3.8"', From b7c77bf4af3f08bb48e9cabd6789357cb7242f9d Mon Sep 17 00:00:00 2001 From: Michael Mintz <mdmintz@gmail.com> Date: Thu, 9 Nov 2023 00:08:26 -0500 Subject: [PATCH 13/13] Version 4.21.0 --- seleniumbase/__version__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/seleniumbase/__version__.py b/seleniumbase/__version__.py index d8beb05b..3384786b 100755 --- a/seleniumbase/__version__.py +++ b/seleniumbase/__version__.py @@ -1,2 +1,2 @@ # seleniumbase package -__version__ = "4.20.9" +__version__ = "4.21.0"