From ef215b6104d176d564bad1fe0b6950643370c33f Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Sun, 23 Jun 2024 13:38:20 -0400 Subject: [PATCH 1/9] Make multiple improvements to UC Mode --- help_docs/method_summary.md | 12 +- help_docs/uc_mode.md | 12 +- seleniumbase/behave/behave_sb.py | 19 ++- seleniumbase/core/browser_launcher.py | 202 ++++++++++++++++++++++-- seleniumbase/fixtures/base_case.py | 118 +++++++++++--- seleniumbase/fixtures/constants.py | 6 + seleniumbase/plugins/driver_manager.py | 76 +++++---- seleniumbase/plugins/pytest_plugin.py | 19 ++- seleniumbase/plugins/sb_manager.py | 123 ++++++++------- seleniumbase/plugins/selenium_plugin.py | 22 +-- seleniumbase/undetected/__init__.py | 5 + 11 files changed, 467 insertions(+), 147 deletions(-) diff --git a/help_docs/method_summary.md b/help_docs/method_summary.md index a577e33d0c9..468d2c93ab7 100644 --- a/help_docs/method_summary.md +++ b/help_docs/method_summary.md @@ -1046,7 +1046,7 @@ driver.uc_open_with_tab(url) # (New tab with default reconnect_time) driver.uc_open_with_reconnect(url, reconnect_time=None) # (New tab) -driver.uc_open_with_disconnect(url) # Open in new tab + disconnect() +driver.uc_open_with_disconnect(url, timeout=None) # New tab + sleep() driver.reconnect(timeout) # disconnect() + sleep(timeout) + connect() @@ -1056,7 +1056,15 @@ driver.connect() # Starts the webdriver service to allow actions again driver.uc_click(selector) # A stealthy click for evading bot-detection -driver.uc_switch_to_frame(frame) # switch_to_frame() in a stealthy way +driver.uc_gui_press_key(key) # Use PyAutoGUI to press the keyboard key + +driver.uc_gui_press_keys(keys) # Use PyAutoGUI to press a list of keys + +driver.uc_gui_write(text) # Similar to uc_gui_press_keys(), but faster + +driver.uc_gui_handle_cf(frame="iframe") # PyAutoGUI click CF Turnstile + +driver.uc_switch_to_frame(frame="iframe") # Stealthy switch_to_frame() ``` -------- diff --git a/help_docs/uc_mode.md b/help_docs/uc_mode.md index 1980d9fb56a..7931f38456f 100644 --- a/help_docs/uc_mode.md +++ b/help_docs/uc_mode.md @@ -159,7 +159,7 @@ driver.uc_open_with_tab(url) driver.uc_open_with_reconnect(url, reconnect_time=None) -driver.uc_open_with_disconnect(url) +driver.uc_open_with_disconnect(url, timeout=None) driver.reconnect(timeout) @@ -171,6 +171,14 @@ driver.uc_click( selector, by="css selector", timeout=settings.SMALL_TIMEOUT, reconnect_time=None) +driver.uc_gui_press_key(key) + +driver.uc_gui_press_keys(keys) + +driver.uc_gui_write(text) + +driver.uc_gui_handle_cf(frame="iframe") + driver.uc_switch_to_frame(frame, reconnect_time=None) ``` @@ -211,6 +219,8 @@ driver.reconnect("breakpoint")
  • Timing. (UC Mode methods let you customize default values that aren't good enough for your environment.)
  • Not using driver.uc_click(selector) when you need to remain undetected while clicking something.
  • +👤 On Linux, you may need to use `driver.uc_gui_handle_cf()` to successfully bypass a Cloudflare CAPTCHA. If there's more than one iframe on that website (and Cloudflare isn't the first one) then put the CSS Selector of that iframe as the first arg to `driver.uc_gui_handle_cf()`. This method uses `pyautogui`. In order for `pyautogui` to focus on the correct element, use `xvfb=True` / `--xvfb` to activate a special virtual display on Linux. + 👤 To find out if UC Mode will work at all on a specific site (before adjusting for timing), load your site with the following script: ```python diff --git a/seleniumbase/behave/behave_sb.py b/seleniumbase/behave/behave_sb.py index bd54740ee1f..7fed5f88be6 100644 --- a/seleniumbase/behave/behave_sb.py +++ b/seleniumbase/behave/behave_sb.py @@ -862,14 +862,17 @@ def get_configured_sb(context): and not sb.headless2 and not sb.xvfb ): - print( - '(Linux uses "-D headless" by default. ' - 'To override, use "-D headed" / "-D gui". ' - 'For Xvfb mode instead, use "-D xvfb". ' - "Or you can hide this info by using" - '"-D headless" / "-D headless2".)' - ) - sb.headless = True + if not sb.undetectable: + print( + '(Linux uses "-D headless" by default. ' + 'To override, use "-D headed" / "-D gui". ' + 'For Xvfb mode instead, use "-D xvfb". ' + "Or you can hide this info by using" + '"-D headless" / "-D headless2" / "-D uc".)' + ) + sb.headless = True + else: + sb.xvfb = True # Recorder Mode can still optimize scripts in --headless2 mode. if sb.recorder_mode and sb.headless: sb.headless = False diff --git a/seleniumbase/core/browser_launcher.py b/seleniumbase/core/browser_launcher.py index 4f37ea64004..ac4b54c543f 100644 --- a/seleniumbase/core/browser_launcher.py +++ b/seleniumbase/core/browser_launcher.py @@ -11,6 +11,7 @@ import warnings from selenium import webdriver from selenium.common.exceptions import ElementClickInterceptedException +from selenium.common.exceptions import InvalidSessionIdException from selenium.webdriver.chrome.service import Service as ChromeService from selenium.webdriver.common.options import ArgOptions from selenium.webdriver.common.service import utils as service_utils @@ -28,6 +29,7 @@ from seleniumbase.core import sb_driver from seleniumbase.fixtures import constants from seleniumbase.fixtures import js_utils +from seleniumbase.fixtures import page_actions from seleniumbase.fixtures import shared_utils urllib3.disable_warnings() @@ -409,7 +411,7 @@ def uc_open(driver, url): if (url.startswith("http:") or url.startswith("https:")): with driver: script = 'window.location.href = "%s";' % url - js_utils.call_me_later(driver, script, 33) + js_utils.call_me_later(driver, script, 5) else: driver.default_get(url) # The original one return None @@ -440,22 +442,28 @@ def uc_open_with_reconnect(driver, url, reconnect_time=None): url = "https://" + url if (url.startswith("http:") or url.startswith("https:")): script = 'window.open("%s","_blank");' % url - js_utils.call_me_later(driver, script, 3) - time.sleep(0.007) + driver.execute_script(script) + time.sleep(0.05) driver.close() if reconnect_time == "disconnect": driver.disconnect() - time.sleep(0.007) + time.sleep(0.008) else: driver.reconnect(reconnect_time) - driver.switch_to.window(driver.window_handles[-1]) + time.sleep(0.004) + try: + driver.switch_to.window(driver.window_handles[-1]) + except InvalidSessionIdException: + time.sleep(0.05) + driver.switch_to.window(driver.window_handles[-1]) else: driver.default_get(url) # The original one return None -def uc_open_with_disconnect(driver, url): +def uc_open_with_disconnect(driver, url, timeout=None): """Open a url and disconnect chromedriver. + Then waits for the duration of the timeout. Note: You can't perform Selenium actions again until after you've called driver.connect().""" if url.startswith("//"): @@ -464,11 +472,16 @@ def uc_open_with_disconnect(driver, url): url = "https://" + url if (url.startswith("http:") or url.startswith("https:")): script = 'window.open("%s","_blank");' % url - js_utils.call_me_later(driver, script, 3) - time.sleep(0.007) + driver.execute_script(script) + time.sleep(0.05) driver.close() driver.disconnect() - time.sleep(0.007) + min_timeout = 0.008 + if timeout and not str(timeout).replace(".", "", 1).isdigit(): + timeout = min_timeout + if not timeout or timeout < min_timeout: + timeout = min_timeout + time.sleep(timeout) else: driver.default_get(url) # The original one return None @@ -490,7 +503,7 @@ def uc_click( pass element = driver.wait_for_selector(selector, by=by, timeout=timeout) tag_name = element.tag_name - if not tag_name == "span": # Element must be "visible" + if not tag_name == "span" and not tag_name == "input": # Must be "visible" element = driver.wait_for_element(selector, by=by, timeout=timeout) try: element.uc_click( @@ -509,7 +522,154 @@ def uc_click( driver.reconnect(reconnect_time) -def uc_switch_to_frame(driver, frame, reconnect_time=None): +def verify_pyautogui_has_a_headed_browser(): + """PyAutoGUI requires a headed browser so that it can + focus on the correct element when performing actions.""" + if sb_config.headless or sb_config.headless2: + raise Exception( + "PyAutoGUI can't be used in headless mode!" + ) + + +def install_pyautogui_if_missing(): + verify_pyautogui_has_a_headed_browser() + pip_find_lock = fasteners.InterProcessLock( + constants.PipInstall.FINDLOCK + ) + with pip_find_lock: # Prevent issues with multiple processes + try: + import pyautogui + try: + use_pyautogui_ver = constants.PyAutoGUI.VER + if pyautogui.__version__ != use_pyautogui_ver: + del pyautogui + shared_utils.pip_install( + "pyautogui", version=use_pyautogui_ver + ) + import pyautogui + except Exception: + pass + except Exception: + print("\nPyAutoGUI required! Installing now...") + shared_utils.pip_install( + "pyautogui", version=constants.PyAutoGUI.VER + ) + + +def get_configured_pyautogui(pyautogui_copy): + if ( + IS_LINUX + and hasattr(pyautogui_copy, "_pyautogui_x11") + and "DISPLAY" in os.environ.keys() + ): + if ( + hasattr(sb_config, "_pyautogui_x11_display") + and sb_config._pyautogui_x11_display + and hasattr(pyautogui_copy._pyautogui_x11, "_display") + and ( + sb_config._pyautogui_x11_display + == pyautogui_copy._pyautogui_x11._display + ) + ): + pass + else: + import Xlib.display + pyautogui_copy._pyautogui_x11._display = ( + Xlib.display.Display(os.environ['DISPLAY']) + ) + sb_config._pyautogui_x11_display = ( + pyautogui_copy._pyautogui_x11._display + ) + return pyautogui_copy + + +def uc_gui_press_key(driver, key): + install_pyautogui_if_missing() + import pyautogui + pyautogui = get_configured_pyautogui(pyautogui) + gui_lock = fasteners.InterProcessLock( + constants.MultiBrowser.PYAUTOGUILOCK + ) + with gui_lock: + pyautogui.press(key) + + +def uc_gui_press_keys(driver, keys): + install_pyautogui_if_missing() + import pyautogui + pyautogui = get_configured_pyautogui(pyautogui) + gui_lock = fasteners.InterProcessLock( + constants.MultiBrowser.PYAUTOGUILOCK + ) + with gui_lock: + for key in keys: + pyautogui.press(key) + + +def uc_gui_write(driver, text): + install_pyautogui_if_missing() + import pyautogui + pyautogui = get_configured_pyautogui(pyautogui) + gui_lock = fasteners.InterProcessLock( + constants.MultiBrowser.PYAUTOGUILOCK + ) + with gui_lock: + pyautogui.write(text) + + +def uc_gui_handle_cf(driver, frame="iframe"): + source = driver.get_page_source() + if ( + "//challenges.cloudflare.com" not in source + and 'aria-label="Cloudflare"' not in source + ): + return + install_pyautogui_if_missing() + import pyautogui + pyautogui = get_configured_pyautogui(pyautogui) + gui_lock = fasteners.InterProcessLock( + constants.MultiBrowser.PYAUTOGUILOCK + ) + with gui_lock: # Prevent issues with multiple processes + needs_switch = False + is_in_frame = js_utils.is_in_frame(driver) + if is_in_frame and driver.is_element_present("#challenge-stage"): + driver.switch_to.parent_frame() + needs_switch = True + is_in_frame = js_utils.is_in_frame(driver) + if not is_in_frame: + # Make sure the window is on top + page_actions.switch_to_window( + driver, + driver.current_window_handle, + timeout=settings.SMALL_TIMEOUT, + ) + if not is_in_frame or needs_switch: + # Currently not in frame (or nested frame outside CF one) + try: + driver.switch_to_frame(frame) + except Exception: + if driver.is_element_present("iframe"): + driver.switch_to_frame("iframe") + else: + return + try: + driver.execute_script('document.querySelector("input").focus()') + except Exception: + try: + driver.switch_to.default_content() + except Exception: + return + driver.disconnect() + try: + pyautogui.press(" ") + except Exception: + pass + reconnect_time = (float(constants.UC.RECONNECT_TIME) / 2.0) + 0.5 + driver.reconnect(reconnect_time) + + +def uc_switch_to_frame(driver, frame="iframe", reconnect_time=None): from selenium.webdriver.remote.webelement import WebElement if isinstance(frame, WebElement): if not reconnect_time: @@ -3822,6 +3982,26 @@ def get_local_driver( driver.uc_click = lambda *args, **kwargs: uc_click( driver, *args, **kwargs ) + driver.uc_gui_press_key = ( + lambda *args, **kwargs: uc_gui_press_key( + driver, *args, **kwargs + ) + ) + driver.uc_gui_press_keys = ( + lambda *args, **kwargs: uc_gui_press_keys( + driver, *args, **kwargs + ) + ) + driver.uc_gui_write = ( + lambda *args, **kwargs: uc_gui_write( + driver, *args, **kwargs + ) + ) + driver.uc_gui_handle_cf = ( + lambda *args, **kwargs: uc_gui_handle_cf( + driver, *args, **kwargs + ) + ) driver.uc_switch_to_frame = ( lambda *args, **kwargs: uc_switch_to_frame( driver, *args, **kwargs diff --git a/seleniumbase/fixtures/base_case.py b/seleniumbase/fixtures/base_case.py index 8a7df9bc917..ba88fe24e30 100644 --- a/seleniumbase/fixtures/base_case.py +++ b/seleniumbase/fixtures/base_case.py @@ -3728,15 +3728,11 @@ def open_new_window(self, switch_to=True): """Opens a new browser tab/window and switches to it by default.""" self.wait_for_ready_state_complete() if switch_to: - if self.undetectable: - self.driver.execute_script("window.open('data:,');") + try: + self.driver.switch_to.new_window("tab") + except Exception: + self.driver.execute_script("window.open('');") self.switch_to_newest_window() - else: - try: - self.driver.switch_to.new_window("tab") - except Exception: - self.driver.execute_script("window.open('');") - self.switch_to_newest_window() else: self.driver.execute_script("window.open('');") time.sleep(0.01) @@ -4166,6 +4162,14 @@ def get_new_driver( self.connect = new_driver.connect if hasattr(new_driver, "uc_click"): self.uc_click = new_driver.uc_click + if hasattr(new_driver, "uc_gui_press_key"): + self.uc_gui_press_key = new_driver.uc_gui_press_key + if hasattr(new_driver, "uc_gui_press_keys"): + self.uc_gui_press_keys = new_driver.uc_gui_press_keys + if hasattr(new_driver, "uc_gui_write"): + self.uc_gui_write = new_driver.uc_gui_write + if hasattr(new_driver, "uc_gui_handle_cf"): + self.uc_gui_handle_cf = new_driver.uc_gui_handle_cf if hasattr(new_driver, "uc_switch_to_frame"): self.uc_switch_to_frame = new_driver.uc_switch_to_frame return new_driver @@ -13681,23 +13685,95 @@ def __highlight_with_assert_success( pass # JQuery probably couldn't load. Skip highlighting. time.sleep(0.065) + def __activate_standard_virtual_display(self): + from sbvirtualdisplay import Display + width = settings.HEADLESS_START_WIDTH + height = settings.HEADLESS_START_HEIGHT + try: + self._xvfb_display = Display( + visible=0, size=(width, height) + ) + self._xvfb_display.start() + sb_config._virtual_display = self._xvfb_display + self.headless_active = True + sb_config.headless_active = True + except Exception: + pass + def __activate_virtual_display_as_needed(self): """Should be needed only on Linux. The "--xvfb" arg is still useful, as it prevents headless mode, which is the default mode on Linux unless using another arg.""" if "linux" in sys.platform and (not self.headed or self.xvfb): - width = settings.HEADLESS_START_WIDTH - height = settings.HEADLESS_START_HEIGHT - try: - from sbvirtualdisplay import Display - - self._xvfb_display = Display(visible=0, size=(width, height)) - self._xvfb_display.start() - sb_config._virtual_display = self._xvfb_display - self.headless_active = True - sb_config.headless_active = True - except Exception: - pass + from sbvirtualdisplay import Display + if self.undetectable and not (self.headless or self.headless2): + import Xlib.display + try: + self._xvfb_display = Display( + visible=True, + size=(1366, 768), + backend="xvfb", + use_xauth=True, + ) + self._xvfb_display.start() + if "DISPLAY" not in os.environ.keys(): + print("\nX11 display failed! Will use regular xvfb!") + self.__activate_standard_virtual_display() + except Exception as e: + if hasattr(e, "msg"): + print("\n" + str(e.msg)) + else: + print(e) + print("\nX11 display failed! Will use regular xvfb!") + self.__activate_standard_virtual_display() + return + pip_find_lock = fasteners.InterProcessLock( + constants.PipInstall.FINDLOCK + ) + with pip_find_lock: # Prevent issues with multiple processes + pyautogui_is_installed = False + try: + import pyautogui + try: + use_pyautogui_ver = constants.PyAutoGUI.VER + if pyautogui.__version__ != use_pyautogui_ver: + del pyautogui # To get newer ver + shared_utils.pip_install( + "pyautogui", version=use_pyautogui_ver + ) + import pyautogui + except Exception: + pass + pyautogui_is_installed = True + except Exception: + message = ( + "PyAutoGUI is required for UC Mode on Linux! " + "Installing now..." + ) + print("\n" + message) + shared_utils.pip_install( + "pyautogui", version=constants.PyAutoGUI.VER + ) + import pyautogui + pyautogui_is_installed = True + if ( + pyautogui_is_installed + and hasattr(pyautogui, "_pyautogui_x11") + ): + try: + pyautogui._pyautogui_x11._display = ( + Xlib.display.Display(os.environ['DISPLAY']) + ) + sb_config._pyautogui_x11_display = ( + pyautogui._pyautogui_x11._display + ) + except Exception as e: + if hasattr(e, "msg"): + print("\n" + str(e.msg)) + else: + print(e) + else: + self.__activate_standard_virtual_display() def __ad_block_as_needed(self): """This is an internal method for handling ad-blocking. @@ -15898,7 +15974,7 @@ def tearDown(self): if self.undetectable: try: self.driver.window_handles - except urllib3.exceptions.MaxRetryError: + except Exception: self.driver.connect() self.__slow_mode_pause_if_active() has_exception = self.__has_exception() diff --git a/seleniumbase/fixtures/constants.py b/seleniumbase/fixtures/constants.py index 8f9b805c0ac..94c8c19068a 100644 --- a/seleniumbase/fixtures/constants.py +++ b/seleniumbase/fixtures/constants.py @@ -164,6 +164,7 @@ class MultiBrowser: CERT_FIXING_LOCK = Files.DOWNLOADS_FOLDER + "/cert_fixing.lock" DOWNLOAD_FILE_LOCK = Files.DOWNLOADS_FOLDER + "/downloading.lock" FILE_IO_LOCK = Files.DOWNLOADS_FOLDER + "/file_io.lock" + PYAUTOGUILOCK = Files.DOWNLOADS_FOLDER + "/pyautogui.lock" class SavedCookies: @@ -354,6 +355,11 @@ class SeleniumWire: BLINKER_VER = "1.7.0" # The "blinker" dependency version +class PyAutoGUI: + # The version installed if PyAutoGUI is not installed + VER = "0.9.54" + + class Mobile: # Default values for mobile settings WIDTH = 390 diff --git a/seleniumbase/plugins/driver_manager.py b/seleniumbase/plugins/driver_manager.py index 90e532d45a4..f36ffe865dc 100644 --- a/seleniumbase/plugins/driver_manager.py +++ b/seleniumbase/plugins/driver_manager.py @@ -36,6 +36,7 @@ ########################################################################### """ +import os import sys @@ -328,37 +329,6 @@ def Driver( ): recorder_mode = True recorder_ext = True - if headed is None: - # Override the default headless mode on Linux if set. - if "--gui" in sys_argv or "--headed" in sys_argv: - headed = True - else: - headed = False - if ( - shared_utils.is_linux() - and not headed - and not headless - and not headless2 - ): - headless = True - if recorder_mode and headless: - headless = False - headless2 = True - if headless2 and browser == "firefox": - headless2 = False # Only for Chromium browsers - headless = True # Firefox has regular headless - elif browser not in ["chrome", "edge"]: - headless2 = False # Only for Chromium browsers - if disable_csp is None: - disable_csp = False - if ( - (enable_ws is None and disable_ws is None) - or (disable_ws is not None and not disable_ws) - or (enable_ws is not None and enable_ws) - ): - enable_ws = True - else: - enable_ws = False if ( undetectable or undetected @@ -414,6 +384,50 @@ def Driver( uc_cdp_events = True else: uc_cdp_events = False + if undetectable and browser != "chrome": + message = ( + '\n Undetected-Chromedriver Mode ONLY supports Chrome!' + '\n ("uc=True" / "undetectable=True" / "--uc")' + '\n (Your browser choice was: "%s".)' + '\n (Will use "%s" without UC Mode.)\n' % (browser, browser) + ) + print(message) + if headed is None: + # Override the default headless mode on Linux if set. + if "--gui" in sys_argv or "--headed" in sys_argv: + headed = True + else: + headed = False + if ( + shared_utils.is_linux() + and not headed + and not headless + and not headless2 + and ( + not undetectable + or "DISPLAY" not in os.environ.keys() + or not os.environ["DISPLAY"] + ) + ): + headless = True + if recorder_mode and headless: + headless = False + headless2 = True + if headless2 and browser == "firefox": + headless2 = False # Only for Chromium browsers + headless = True # Firefox has regular headless + elif browser not in ["chrome", "edge"]: + headless2 = False # Only for Chromium browsers + if disable_csp is None: + disable_csp = False + if ( + (enable_ws is None and disable_ws is None) + or (disable_ws is not None and not disable_ws) + or (enable_ws is not None and enable_ws) + ): + enable_ws = True + else: + enable_ws = False if log_cdp_events is None and log_cdp is None: if ( "--log-cdp-events" in sys_argv diff --git a/seleniumbase/plugins/pytest_plugin.py b/seleniumbase/plugins/pytest_plugin.py index a6004c6f55b..6e9e803790c 100644 --- a/seleniumbase/plugins/pytest_plugin.py +++ b/seleniumbase/plugins/pytest_plugin.py @@ -1703,14 +1703,17 @@ def pytest_configure(config): and not sb_config.headless2 and not sb_config.xvfb ): - print( - "(Linux uses --headless by default. " - "To override, use --headed / --gui. " - "For Xvfb mode instead, use --xvfb. " - "Or you can hide this info by using " - "--headless / --headless2.)" - ) - sb_config.headless = True + if not sb_config.undetectable: + print( + "(Linux uses --headless by default. " + "To override, use --headed / --gui. " + "For Xvfb mode instead, use --xvfb. " + "Or you can hide this info by using " + "--headless / --headless2 / --uc.)" + ) + sb_config.headless = True + else: + sb_config.xvfb = True # Recorder Mode can still optimize scripts in --headless2 mode. if sb_config.recorder_mode and sb_config.headless: diff --git a/seleniumbase/plugins/sb_manager.py b/seleniumbase/plugins/sb_manager.py index 2fd8d9133e0..66b2fad84c1 100644 --- a/seleniumbase/plugins/sb_manager.py +++ b/seleniumbase/plugins/sb_manager.py @@ -386,6 +386,69 @@ def SB( if not shared_utils.is_linux(): # The Xvfb virtual display server is for Linux OS Only! xvfb = False + if ( + undetectable + or undetected + or uc + or uc_cdp_events + or uc_cdp + or uc_subprocess + or uc_sub + ): + undetectable = True + if ( + (undetectable or undetected or uc) + and (uc_subprocess is None) + and (uc_sub is None) + ): + uc_subprocess = True # Use UC as a subprocess by default. + elif ( + "--undetectable" in sys_argv + or "--undetected" in sys_argv + or "--uc" in sys_argv + or "--uc-cdp-events" in sys_argv + or "--uc_cdp_events" in sys_argv + or "--uc-cdp" in sys_argv + or "--uc-subprocess" in sys_argv + or "--uc_subprocess" in sys_argv + or "--uc-sub" in sys_argv + ): + undetectable = True + if uc_subprocess is None and uc_sub is None: + uc_subprocess = True # Use UC as a subprocess by default. + else: + undetectable = False + if uc_subprocess or uc_sub: + uc_subprocess = True + elif ( + "--uc-subprocess" in sys_argv + or "--uc_subprocess" in sys_argv + or "--uc-sub" in sys_argv + ): + uc_subprocess = True + else: + uc_subprocess = False + if uc_cdp_events or uc_cdp: + undetectable = True + uc_cdp_events = True + elif ( + "--uc-cdp-events" in sys_argv + or "--uc_cdp_events" in sys_argv + or "--uc-cdp" in sys_argv + or "--uc_cdp" in sys_argv + ): + undetectable = True + uc_cdp_events = True + else: + uc_cdp_events = False + if undetectable and browser != "chrome": + message = ( + '\n Undetected-Chromedriver Mode ONLY supports Chrome!' + '\n ("uc=True" / "undetectable=True" / "--uc")' + '\n (Your browser choice was: "%s".)' + '\n (Will use "%s" without UC Mode.)\n' % (browser, browser) + ) + print(message) if headed is None: # Override the default headless mode on Linux if set. if "--gui" in sys_argv or "--headed" in sys_argv: @@ -399,7 +462,10 @@ def SB( and not headless2 and not xvfb ): - headless = True + if not undetectable: + headless = True + else: + xvfb = True if headless2 and browser == "firefox": headless2 = False # Only for Chromium browsers headless = True # Firefox has regular headless @@ -456,61 +522,6 @@ def SB( else: enable_ws = False disable_ws = True - if ( - undetectable - or undetected - or uc - or uc_cdp_events - or uc_cdp - or uc_subprocess - or uc_sub - ): - undetectable = True - if ( - (undetectable or undetected or uc) - and (uc_subprocess is None) - and (uc_sub is None) - ): - uc_subprocess = True # Use UC as a subprocess by default. - elif ( - "--undetectable" in sys_argv - or "--undetected" in sys_argv - or "--uc" in sys_argv - or "--uc-cdp-events" in sys_argv - or "--uc_cdp_events" in sys_argv - or "--uc-cdp" in sys_argv - or "--uc-subprocess" in sys_argv - or "--uc_subprocess" in sys_argv - or "--uc-sub" in sys_argv - ): - undetectable = True - if uc_subprocess is None and uc_sub is None: - uc_subprocess = True # Use UC as a subprocess by default. - else: - undetectable = False - if uc_subprocess or uc_sub: - uc_subprocess = True - elif ( - "--uc-subprocess" in sys_argv - or "--uc_subprocess" in sys_argv - or "--uc-sub" in sys_argv - ): - uc_subprocess = True - else: - uc_subprocess = False - if uc_cdp_events or uc_cdp: - undetectable = True - uc_cdp_events = True - elif ( - "--uc-cdp-events" in sys_argv - or "--uc_cdp_events" in sys_argv - or "--uc-cdp" in sys_argv - or "--uc_cdp" in sys_argv - ): - undetectable = True - uc_cdp_events = True - else: - uc_cdp_events = False if log_cdp_events is None and log_cdp is None: if ( "--log-cdp-events" in sys_argv diff --git a/seleniumbase/plugins/selenium_plugin.py b/seleniumbase/plugins/selenium_plugin.py index c3cf01f5fc9..b169316c616 100644 --- a/seleniumbase/plugins/selenium_plugin.py +++ b/seleniumbase/plugins/selenium_plugin.py @@ -1204,15 +1204,19 @@ def beforeTest(self, test): and not self.options.headless2 and not self.options.xvfb ): - print( - "(Linux uses --headless by default. " - "To override, use --headed / --gui. " - "For Xvfb mode instead, use --xvfb. " - "Or you can hide this info by using " - "--headless / --headless2.)" - ) - self.options.headless = True - test.test.headless = True + if not self.options.undetectable: + print( + "(Linux uses --headless by default. " + "To override, use --headed / --gui. " + "For Xvfb mode instead, use --xvfb. " + "Or you can hide this info by using " + "--headless / --headless2 / --uc.)" + ) + self.options.headless = True + test.test.headless = True + else: + self.options.xvfb = True + test.test.xvfb = True if self.options.use_wire and self.options.undetectable: print( "\n" diff --git a/seleniumbase/undetected/__init__.py b/seleniumbase/undetected/__init__.py index 7e58bc3786f..be6f4fb0766 100644 --- a/seleniumbase/undetected/__init__.py +++ b/seleniumbase/undetected/__init__.py @@ -437,10 +437,12 @@ def reconnect(self, timeout=0.1): self.service.start() except Exception: pass + time.sleep(0.012) try: self.start_session() except Exception: pass + time.sleep(0.012) def disconnect(self): """Stops the chromedriver service that runs in the background. @@ -450,6 +452,7 @@ def disconnect(self): self.service.stop() except Exception: pass + time.sleep(0.012) def connect(self): """Starts the chromedriver service that runs in the background @@ -459,10 +462,12 @@ def connect(self): self.service.start() except Exception: pass + time.sleep(0.012) try: self.start_session() except Exception: pass + time.sleep(0.012) def start_session(self, capabilities=None): if not capabilities: From db556c11948a6bc68863b83e49f853e4d4677c8e Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Sun, 23 Jun 2024 13:39:33 -0400 Subject: [PATCH 2/9] Update the Recorder --- seleniumbase/extensions/recorder.zip | Bin 11908 -> 11918 bytes seleniumbase/js_code/active_css_js.py | 2 +- seleniumbase/js_code/recorder_js.py | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/seleniumbase/extensions/recorder.zip b/seleniumbase/extensions/recorder.zip index 31f243ad84c4c797d6d093b5cb64301c5750701d..257fcd562d839a5d9143d5c89e05f025d5479803 100644 GIT binary patch delta 5831 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 Cx&Uzi delta 5763 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 Date: Sun, 23 Jun 2024 13:43:13 -0400 Subject: [PATCH 3/9] Refactoring --- seleniumbase/__init__.py | 3 ++ seleniumbase/core/browser_launcher.py | 6 ++-- seleniumbase/fixtures/base_case.py | 20 ++++++++++--- seleniumbase/fixtures/page_actions.py | 40 ++++++++++++++++++++++++-- seleniumbase/plugins/driver_manager.py | 3 ++ seleniumbase/plugins/sb_manager.py | 3 ++ 6 files changed, 65 insertions(+), 10 deletions(-) diff --git a/seleniumbase/__init__.py b/seleniumbase/__init__.py index fe64cffabac..e84a7d3628c 100644 --- a/seleniumbase/__init__.py +++ b/seleniumbase/__init__.py @@ -1,4 +1,5 @@ import collections +import os import pdb try: import pdbp # (Pdb+) --- Python Debugger Plus @@ -34,11 +35,13 @@ pdb.DefaultConfig.truncate_long_lines = False pdb.DefaultConfig.sticky_by_default = True colored_traceback.add_hook() +os.environ["SE_AVOID_STATS"] = "true" # Disable Selenium Manager stats if sys.version_info >= (3, 7): webdriver.TouchActions = None # Lifeline for past selenium-wire versions if sys.version_info >= (3, 10): collections.Callable = collections.abc.Callable # Lifeline for nosetests del collections # Undo "import collections" / Simplify "dir(seleniumbase)" +del os # Undo "import os" / Simplify "dir(seleniumbase)" del sys # Undo "import sys" / Simplify "dir(seleniumbase)" del webdriver # Undo "import webdriver" / Simplify "dir(seleniumbase)" diff --git a/seleniumbase/core/browser_launcher.py b/seleniumbase/core/browser_launcher.py index ac4b54c543f..ee8abbf8501 100644 --- a/seleniumbase/core/browser_launcher.py +++ b/seleniumbase/core/browser_launcher.py @@ -1007,7 +1007,7 @@ def _set_chrome_options( prefs["download.prompt_for_download"] = False prefs["credentials_enable_service"] = False prefs["local_discovery.notifications_enabled"] = False - prefs["safebrowsing.enabled"] = False + prefs["safebrowsing.enabled"] = False # Prevent PW "data breach" pop-ups prefs["safebrowsing.disable_download_protection"] = True prefs["omnibox-max-zero-suggest-matches"] = 0 prefs["omnibox-use-existing-autocomplete-client"] = 0 @@ -1534,7 +1534,7 @@ def _set_firefox_options( f_pref_value = False elif f_pref_value.isdigit(): f_pref_value = int(f_pref_value) - elif f_pref_value.isdecimal(): + elif f_pref_value.replace(".", "", 1).isdigit(): f_pref_value = float(f_pref_value) else: pass # keep as string @@ -2598,7 +2598,7 @@ def get_local_driver( "credentials_enable_service": False, "local_discovery.notifications_enabled": False, "safebrowsing.disable_download_protection": True, - "safebrowsing.enabled": False, + "safebrowsing.enabled": False, # Prevent PW "data breach" pop-ups "omnibox-max-zero-suggest-matches": 0, "omnibox-use-existing-autocomplete-client": 0, "omnibox-trending-zero-prefix-suggestions-on-ntp": 0, diff --git a/seleniumbase/fixtures/base_case.py b/seleniumbase/fixtures/base_case.py index ba88fe24e30..1af4806f8a5 100644 --- a/seleniumbase/fixtures/base_case.py +++ b/seleniumbase/fixtures/base_case.py @@ -1251,9 +1251,17 @@ def focus(self, selector, by="css selector", timeout=None): if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT: timeout = self.__get_new_timeout(timeout) selector, by = self.__recalculate_selector(selector, by) - element = self.wait_for_element_visible( + element = self.wait_for_element_present( selector, by=by, timeout=timeout ) + if not element.is_displayed(): + css_selector = self.convert_to_css_selector(selector, by=by) + css_selector = re.escape(css_selector) # Add "\\" to special chars + css_selector = self.__escape_quotes_if_needed(css_selector) + script = """document.querySelector('%s').focus();""" % css_selector + self.execute_script(script) + self.__demo_mode_pause_if_active() + return self.scroll_to(selector, by=by, timeout=timeout) try: element.send_keys(Keys.NULL) @@ -1828,7 +1836,7 @@ def click_partial_link_text(self, partial_link_text, timeout=None): elif self.slow_mode: self.__slow_mode_pause_if_active() - def get_text(self, selector, by="css selector", timeout=None): + def get_text(self, selector="html", by="css selector", timeout=None): self.__check_scope() if not timeout: timeout = settings.LARGE_TIMEOUT @@ -2083,7 +2091,9 @@ def get_property( return "" return property_value - def get_text_content(self, selector, by="css selector", timeout=None): + def get_text_content( + self, selector="html", by="css selector", timeout=None + ): """Returns the text that appears in the HTML for an element. This is different from "self.get_text(selector, by="css selector")" because that only returns the visible text on a page for an element, @@ -3401,7 +3411,7 @@ def maximize_window(self): self.driver.maximize_window() self.__demo_mode_pause_if_active() - def switch_to_frame(self, frame, timeout=None): + def switch_to_frame(self, frame="iframe", timeout=None): """Wait for an iframe to appear, and switch to it. This should be usable as a drop-in replacement for driver.switch_to.frame(). The iframe identifier can be a selector, an index, an id, a name, @@ -6812,9 +6822,11 @@ def get_pdf_text( try: import cryptography if cryptography.__version__ != "39.0.2": + del cryptography # To get newer ver shared_utils.pip_install( "cryptography", version="39.0.2" ) + import cryptography except Exception: shared_utils.pip_install( "cryptography", version="39.0.2" diff --git a/seleniumbase/fixtures/page_actions.py b/seleniumbase/fixtures/page_actions.py index 3aaa2ccf98a..9564763388c 100644 --- a/seleniumbase/fixtures/page_actions.py +++ b/seleniumbase/fixtures/page_actions.py @@ -1017,7 +1017,7 @@ def wait_for_element_not_visible( def wait_for_text_not_visible( driver, text, - selector, + selector="html", by="css selector", timeout=settings.LARGE_TIMEOUT, ): @@ -1060,7 +1060,7 @@ def wait_for_text_not_visible( def wait_for_exact_text_not_visible( driver, text, - selector, + selector="html", by="css selector", timeout=settings.LARGE_TIMEOUT, ): @@ -1686,19 +1686,50 @@ def assert_text( by="css selector", timeout=settings.SMALL_TIMEOUT, ): + selector, by = page_utils.recalculate_selector(selector, by) wait_for_text_visible( driver, text.strip(), selector, by=by, timeout=timeout ) def assert_exact_text( - driver, text, selector, by="css selector", timeout=settings.SMALL_TIMEOUT + driver, + text, + selector="html", + by="css selector", + timeout=settings.SMALL_TIMEOUT, ): + selector, by = page_utils.recalculate_selector(selector, by) wait_for_exact_text_visible( driver, text.strip(), selector, by=by, timeout=timeout ) +def assert_non_empty_text( + driver, + selector, + by="css selector", + timeout=settings.SMALL_TIMEOUT, +): + selector, by = page_utils.recalculate_selector(selector, by) + wait_for_non_empty_text_visible( + driver, selector, by=by, timeout=timeout + ) + + +def assert_text_not_visible( + driver, + text, + selector="html", + by="css selector", + timeout=settings.SMALL_TIMEOUT, +): + selector, by = page_utils.recalculate_selector(selector, by) + wait_for_text_not_visible( + driver, text.strip(), selector, by=by, timeout=timeout + ) + + def wait_for_element( driver, selector, @@ -1748,6 +1779,7 @@ def wait_for_text( by="css selector", timeout=settings.LARGE_TIMEOUT, ): + selector, by = page_utils.recalculate_selector(selector, by) return wait_for_text_visible( driver=driver, text=text, @@ -1764,6 +1796,7 @@ def wait_for_exact_text( by="css selector", timeout=settings.LARGE_TIMEOUT, ): + selector, by = page_utils.recalculate_selector(selector, by) return wait_for_exact_text_visible( driver=driver, text=text, @@ -1779,6 +1812,7 @@ def wait_for_non_empty_text( by="css selector", timeout=settings.LARGE_TIMEOUT, ): + selector, by = page_utils.recalculate_selector(selector, by) return wait_for_non_empty_text_visible( driver=driver, selector=selector, diff --git a/seleniumbase/plugins/driver_manager.py b/seleniumbase/plugins/driver_manager.py index f36ffe865dc..598077f362c 100644 --- a/seleniumbase/plugins/driver_manager.py +++ b/seleniumbase/plugins/driver_manager.py @@ -125,6 +125,7 @@ def Driver( uc_cdp=None, # Shortcut / Duplicate of "uc_cdp_events". uc_sub=None, # Shortcut / Duplicate of "uc_subprocess". log_cdp=None, # Shortcut / Duplicate of "log_cdp_events". + server=None, # Shortcut / Duplicate of "servername". wire=None, # Shortcut / Duplicate of "use_wire". pls=None, # Shortcut / Duplicate of "page_load_strategy". ): @@ -247,6 +248,8 @@ def Driver( headless2 = False if protocol is None: protocol = "http" # For the Selenium Grid only! + if server is not None and servername is None: + servername = server if servername is None: servername = "localhost" # For the Selenium Grid only! use_grid = False diff --git a/seleniumbase/plugins/sb_manager.py b/seleniumbase/plugins/sb_manager.py index 66b2fad84c1..b823d564c2d 100644 --- a/seleniumbase/plugins/sb_manager.py +++ b/seleniumbase/plugins/sb_manager.py @@ -103,6 +103,7 @@ def SB( uc_cdp=None, # Shortcut / Duplicate of "uc_cdp_events". uc_sub=None, # Shortcut / Duplicate of "uc_subprocess". log_cdp=None, # Shortcut / Duplicate of "log_cdp_events". + server=None, # Shortcut / Duplicate of "servername". wire=None, # Shortcut / Duplicate of "use_wire". pls=None, # Shortcut / Duplicate of "page_load_strategy". sjw=None, # Shortcut / Duplicate of "skip_js_waits". @@ -282,6 +283,8 @@ def SB( headless2 = False if protocol is None: protocol = "http" # For the Selenium Grid only! + if server is not None and servername is None: + servername = server if servername is None: servername = "localhost" # For the Selenium Grid only! if port is None: From 711987ef590bebccad9cc7bfa5f5dbe8bc77ce15 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Sun, 23 Jun 2024 13:44:21 -0400 Subject: [PATCH 4/9] Add more sb methods into the driver --- seleniumbase/core/browser_launcher.py | 26 ++++++++++- seleniumbase/core/sb_driver.py | 63 ++++++++++++++++++++++++--- 2 files changed, 83 insertions(+), 6 deletions(-) diff --git a/seleniumbase/core/browser_launcher.py b/seleniumbase/core/browser_launcher.py index ee8abbf8501..37906921d84 100644 --- a/seleniumbase/core/browser_launcher.py +++ b/seleniumbase/core/browser_launcher.py @@ -132,9 +132,13 @@ def extend_driver(driver): page.assert_element_not_visible = DM.assert_element_not_visible page.assert_text = DM.assert_text page.assert_exact_text = DM.assert_exact_text + page.assert_non_empty_text = DM.assert_non_empty_text + page.assert_text_not_visible = DM.assert_text_not_visible page.wait_for_element = DM.wait_for_element page.wait_for_text = DM.wait_for_text page.wait_for_exact_text = DM.wait_for_exact_text + page.wait_for_non_empty_text = DM.wait_for_non_empty_text + page.wait_for_text_not_visible = DM.wait_for_text_not_visible page.wait_for_and_accept_alert = DM.wait_for_and_accept_alert page.wait_for_and_dismiss_alert = DM.wait_for_and_dismiss_alert page.is_element_present = DM.is_element_present @@ -142,12 +146,20 @@ def extend_driver(driver): page.is_text_visible = DM.is_text_visible page.is_exact_text_visible = DM.is_exact_text_visible page.is_attribute_present = DM.is_attribute_present + page.is_non_empty_text_visible = DM.is_non_empty_text_visible page.get_text = DM.get_text page.find_element = DM.find_element page.find_elements = DM.find_elements page.locator = DM.locator page.get_page_source = DM.get_page_source page.get_title = DM.get_title + page.switch_to_default_window = DM.switch_to_default_window + page.switch_to_newest_window = DM.switch_to_newest_window + page.open_new_window = DM.open_new_window + page.open_new_tab = DM.open_new_tab + page.switch_to_window = DM.switch_to_window + page.switch_to_tab = DM.switch_to_tab + page.switch_to_frame = DM.switch_to_frame driver.page = page js = types.SimpleNamespace() js.js_click = DM.js_click @@ -171,12 +183,16 @@ def extend_driver(driver): driver.assert_element_not_visible = DM.assert_element_not_visible driver.assert_text = DM.assert_text driver.assert_exact_text = DM.assert_exact_text + driver.assert_non_empty_text = DM.assert_non_empty_text + driver.assert_text_not_visible = DM.assert_text_not_visible driver.wait_for_element = DM.wait_for_element driver.wait_for_element_visible = DM.wait_for_element_visible driver.wait_for_element_present = DM.wait_for_element_present driver.wait_for_selector = DM.wait_for_selector driver.wait_for_text = DM.wait_for_text driver.wait_for_exact_text = DM.wait_for_exact_text + driver.wait_for_non_empty_text = DM.wait_for_non_empty_text + driver.wait_for_text_not_visible = DM.wait_for_text_not_visible driver.wait_for_and_accept_alert = DM.wait_for_and_accept_alert driver.wait_for_and_dismiss_alert = DM.wait_for_and_dismiss_alert driver.is_element_present = DM.is_element_present @@ -184,8 +200,10 @@ def extend_driver(driver): driver.is_text_visible = DM.is_text_visible driver.is_exact_text_visible = DM.is_exact_text_visible driver.is_attribute_present = DM.is_attribute_present - driver.get_text = DM.get_text + driver.is_non_empty_text_visible = DM.is_non_empty_text_visible + driver.is_online = DM.is_online driver.js_click = DM.js_click + driver.get_text = DM.get_text driver.get_active_element_css = DM.get_active_element_css driver.get_locale_code = DM.get_locale_code driver.get_origin = DM.get_origin @@ -197,6 +215,12 @@ def extend_driver(driver): driver.get_attribute = DM.get_attribute driver.get_page_source = DM.get_page_source driver.get_title = DM.get_title + driver.switch_to_default_window = DM.switch_to_default_window + driver.switch_to_newest_window = DM.switch_to_newest_window + driver.open_new_window = DM.open_new_window + driver.open_new_tab = DM.open_new_tab + driver.switch_to_window = DM.switch_to_window + driver.switch_to_tab = DM.switch_to_tab driver.switch_to_frame = DM.switch_to_frame if hasattr(driver, "proxy"): driver.set_wire_proxy = DM.set_wire_proxy diff --git a/seleniumbase/core/sb_driver.py b/seleniumbase/core/sb_driver.py index f5054173fd8..3ca7a4955f1 100644 --- a/seleniumbase/core/sb_driver.py +++ b/seleniumbase/core/sb_driver.py @@ -94,6 +94,16 @@ def assert_text(self, *args, **kwargs): def assert_exact_text(self, *args, **kwargs): page_actions.assert_exact_text(self.driver, *args, **kwargs) + def assert_non_empty_text(self, *args, **kwargs): + return page_actions.assert_non_empty_text( + self.driver, *args, **kwargs + ) + + def assert_text_not_visible(self, *args, **kwargs): + return page_actions.assert_text_not_visible( + self.driver, *args, **kwargs + ) + def wait_for_element(self, *args, **kwargs): return page_actions.wait_for_element(self.driver, *args, **kwargs) @@ -112,6 +122,16 @@ def wait_for_text(self, *args, **kwargs): def wait_for_exact_text(self, *args, **kwargs): return page_actions.wait_for_exact_text(self.driver, *args, **kwargs) + def wait_for_non_empty_text(self, *args, **kwargs): + return page_actions.wait_for_non_empty_text( + self.driver, *args, **kwargs + ) + + def wait_for_text_not_visible(self, *args, **kwargs): + return page_actions.wait_for_text_not_visible( + self.driver, *args, **kwargs + ) + def wait_for_and_accept_alert(self, *args, **kwargs): return page_actions.wait_for_and_accept_alert( self.driver, *args, **kwargs @@ -134,14 +154,22 @@ def is_text_visible(self, *args, **kwargs): def is_exact_text_visible(self, *args, **kwargs): return page_actions.is_exact_text_visible(self.driver, *args, **kwargs) - def get_text(self, *args, **kwargs): - return page_actions.get_text(self.driver, *args, **kwargs) + def is_attribute_present(self, *args, **kwargs): + return page_actions.has_attribute(self.driver, *args, **kwargs) + + def is_non_empty_text_visible(self, *args, **kwargs): + return page_actions.is_non_empty_text_visible( + self.driver, *args, **kwargs + ) + + def is_online(self): + return self.driver.execute_script("return navigator.onLine;") def js_click(self, *args, **kwargs): return page_actions.js_click(self.driver, *args, **kwargs) - def is_attribute_present(self, *args, **kwargs): - return page_actions.has_attribute(self.driver, *args, **kwargs) + def get_text(self, *args, **kwargs): + return page_actions.get_text(self.driver, *args, **kwargs) def get_active_element_css(self, *args, **kwargs): return js_utils.get_active_element_css(self.driver, *args, **kwargs) @@ -182,7 +210,32 @@ def highlight_if_visible( if self.is_element_visible(selector, by=by): self.highlight(selector, by=by, loops=loops, scroll=scroll) - def switch_to_frame(self, frame): + def switch_to_default_window(self): + self.driver.switch_to.window(self.driver.window_handles[0]) + + def switch_to_newest_window(self): + self.driver.switch_to.window(self.driver.window_handles[-1]) + + def open_new_window(self, switch_to=True): + if switch_to: + try: + self.driver.switch_to.new_window("tab") + except Exception: + self.driver.execute_script("window.open('');") + self.switch_to_newest_window() + else: + self.driver.execute_script("window.open('');") + + def open_new_tab(self, switch_to=True): + self.open_new_window(switch_to=switch_to) + + def switch_to_window(self, *args, **kwargs): + page_actions.switch_to_window(self.driver, *args, **kwargs) + + def switch_to_tab(self, *args, **kwargs): + self.switch_to_window(*args, **kwargs) + + def switch_to_frame(self, frame="iframe"): if isinstance(frame, WebElement): self.driver.switch_to.frame(frame) else: From 0b8c06889f0d38ae3aa0bc2ddd4afb62da306295 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Sun, 23 Jun 2024 13:44:51 -0400 Subject: [PATCH 5/9] Update Chromium options --- seleniumbase/core/browser_launcher.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/seleniumbase/core/browser_launcher.py b/seleniumbase/core/browser_launcher.py index 37906921d84..fe9023a4ab2 100644 --- a/seleniumbase/core/browser_launcher.py +++ b/seleniumbase/core/browser_launcher.py @@ -1329,6 +1329,7 @@ def _set_chrome_options( chrome_options.add_argument("--auto-open-devtools-for-tabs") if user_agent: chrome_options.add_argument("--user-agent=%s" % user_agent) + chrome_options.add_argument("--safebrowsing-disable-download-protection") chrome_options.add_argument("--disable-browser-side-navigation") chrome_options.add_argument("--disable-save-password-bubble") chrome_options.add_argument("--disable-single-click-autofill") @@ -1367,10 +1368,9 @@ def _set_chrome_options( included_disabled_features.append("DownloadBubbleV2") included_disabled_features.append("InsecureDownloadWarnings") included_disabled_features.append("InterestFeedContentSuggestions") - if user_data_dir: - included_disabled_features.append("PrivacySandboxSettings4") - if not is_using_uc(undetectable, browser_name) or user_data_dir: - included_disabled_features.append("SidePanelPinning") + included_disabled_features.append("PrivacySandboxSettings4") + included_disabled_features.append("SidePanelPinning") + included_disabled_features.append("UserAgentClientHint") for item in extra_disabled_features: if item not in included_disabled_features: included_disabled_features.append(item) @@ -2891,6 +2891,7 @@ def get_local_driver( edge_options.add_argument( "--disable-autofill-keyboard-accessory-view[8]" ) + edge_options.add_argument("--safebrowsing-disable-download-protection") edge_options.add_argument("--disable-browser-side-navigation") edge_options.add_argument("--disable-translate") if not enable_ws: @@ -3032,10 +3033,9 @@ def get_local_driver( included_disabled_features.append("OptimizationGuideModelDownloading") included_disabled_features.append("InsecureDownloadWarnings") included_disabled_features.append("InterestFeedContentSuggestions") - if user_data_dir: - included_disabled_features.append("PrivacySandboxSettings4") - if not is_using_uc(undetectable, browser_name) or user_data_dir: - included_disabled_features.append("SidePanelPinning") + included_disabled_features.append("PrivacySandboxSettings4") + included_disabled_features.append("SidePanelPinning") + included_disabled_features.append("UserAgentClientHint") for item in extra_disabled_features: if item not in included_disabled_features: included_disabled_features.append(item) From 409fa481e3a770e43bce71adce3e4c5a9ae89d4e Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Sun, 23 Jun 2024 13:45:58 -0400 Subject: [PATCH 6/9] Refresh Python dependencies --- mkdocs_build/requirements.txt | 6 +++--- requirements.txt | 22 +++++++++++++--------- setup.py | 28 ++++++++++++++++++---------- 3 files changed, 34 insertions(+), 22 deletions(-) diff --git a/mkdocs_build/requirements.txt b/mkdocs_build/requirements.txt index 6a932c7a84d..73f59a3b895 100644 --- a/mkdocs_build/requirements.txt +++ b/mkdocs_build/requirements.txt @@ -3,7 +3,7 @@ regex>=2024.5.15 pymdown-extensions>=10.8.1 -pipdeptree>=2.22.0 +pipdeptree>=2.23.0 python-dateutil>=2.8.2 Markdown==3.6 markdown2==2.4.13 @@ -12,7 +12,7 @@ Jinja2==3.1.4 click==8.1.7 ghp-import==2.1.0 watchdog==4.0.1 -cairocffi==1.7.0 +cairocffi==1.7.1 pathspec==0.12.1 Babel==2.15.0 paginate==0.5.6 @@ -20,7 +20,7 @@ lxml==5.2.2 pyquery==2.0.0 readtime==3.0.0 mkdocs==1.6.0 -mkdocs-material==9.5.26 +mkdocs-material==9.5.27 mkdocs-exclude-search==0.6.6 mkdocs-simple-hooks==0.1.5 mkdocs-material-extensions==1.3.1 diff --git a/requirements.txt b/requirements.txt index 4cba1a54db7..56b90063bb4 100755 --- a/requirements.txt +++ b/requirements.txt @@ -1,18 +1,20 @@ -pip>=24.0 -packaging>=24.0 +pip>=24.0;python_version<"3.8" +pip>=24.1;python_version>="3.8" +packaging>=24.0;python_version<"3.8" +packaging>=24.1;python_version>="3.8" setuptools>=68.0.0;python_version<"3.8" -setuptools>=70.0.0;python_version>="3.8" +setuptools>=70.1.0;python_version>="3.8" wheel>=0.42.0;python_version<"3.8" wheel>=0.43.0;python_version>="3.8" attrs>=23.2.0 certifi>=2024.6.2 exceptiongroup>=1.2.1 filelock>=3.12.2;python_version<"3.8" -filelock>=3.14.0;python_version>="3.8" +filelock>=3.15.4;python_version>="3.8" platformdirs>=4.0.0;python_version<"3.8" platformdirs>=4.2.2;python_version>="3.8" typing-extensions>=4.12.2;python_version>="3.8" -parse>=1.20.1 +parse>=1.20.2 parse-type>=0.6.2 pyyaml>=6.0.1 six==1.16.0 @@ -30,8 +32,9 @@ trio==0.22.2;python_version<"3.8" trio==0.25.1;python_version>="3.8" trio-websocket==0.11.1 wsproto==1.2.0 +websocket-client==1.8.0;python_version>="3.8" selenium==4.11.2;python_version<"3.8" -selenium==4.21.0;python_version>="3.8" +selenium==4.22.0;python_version>="3.8" cssselect==1.2.0 sortedcontainers==2.4.0 fasteners==0.19 @@ -64,6 +67,7 @@ tabcompleter==1.3.0 pdbp==1.5.0 colorama==0.4.6 pyotp==2.9.0 +python-xlib==0.33;platform_system=="Linux" markdown-it-py==2.2.0;python_version<"3.8" markdown-it-py==3.0.0;python_version>="3.8" mdurl==0.1.2 @@ -73,13 +77,13 @@ rich==13.7.1 # ("pip install -r requirements.txt" also installs this, but "pip install -e ." won't.) coverage==7.2.7;python_version<"3.8" -coverage>=7.5.3;python_version>="3.8" +coverage>=7.5.4;python_version>="3.8" pytest-cov==4.1.0;python_version<"3.8" pytest-cov>=5.0.0;python_version>="3.8" flake8==5.0.4;python_version<"3.9" -flake8==7.0.0;python_version>="3.9" +flake8==7.1.0;python_version>="3.9" mccabe==0.7.0 pyflakes==2.5.0;python_version<"3.9" pyflakes==3.2.0;python_version>="3.9" pycodestyle==2.9.1;python_version<"3.9" -pycodestyle==2.11.1;python_version>="3.9" +pycodestyle==2.12.0;python_version>="3.9" diff --git a/setup.py b/setup.py index 79f7d56935d..fb73d9e309a 100755 --- a/setup.py +++ b/setup.py @@ -34,7 +34,7 @@ print("\nERROR! Publishing to PyPI requires Python>=3.9") sys.exit() print("\n*** Checking code health with flake8:\n") - os.system("python -m pip install 'flake8==7.0.0'") + os.system("python -m pip install 'flake8==7.1.0'") flake8_status = os.system("flake8 --exclude=recordings,temp") if flake8_status != 0: print("\nERROR! Fix flake8 issues before publishing to PyPI!\n") @@ -146,21 +146,23 @@ ], python_requires=">=3.7", install_requires=[ - 'pip>=24.0', - 'packaging>=24.0', + 'pip>=24.0;python_version<"3.8"', + 'pip>=24.1;python_version>="3.8"', + 'packaging>=24.0;python_version<"3.8"', + 'packaging>=24.1;python_version>="3.8"', 'setuptools>=68.0.0;python_version<"3.8"', - 'setuptools>=70.0.0;python_version>="3.8"', + 'setuptools>=70.1.0;python_version>="3.8"', 'wheel>=0.42.0;python_version<"3.8"', 'wheel>=0.43.0;python_version>="3.8"', 'attrs>=23.2.0', "certifi>=2024.6.2", "exceptiongroup>=1.2.1", 'filelock>=3.12.2;python_version<"3.8"', - 'filelock>=3.14.0;python_version>="3.8"', + 'filelock>=3.15.4;python_version>="3.8"', 'platformdirs>=4.0.0;python_version<"3.8"', 'platformdirs>=4.2.2;python_version>="3.8"', 'typing-extensions>=4.12.2;python_version>="3.8"', - 'parse>=1.20.1', + 'parse>=1.20.2', 'parse-type>=0.6.2', 'pyyaml>=6.0.1', "six==1.16.0", @@ -178,8 +180,9 @@ 'trio==0.25.1;python_version>="3.8"', 'trio-websocket==0.11.1', 'wsproto==1.2.0', + 'websocket-client==1.8.0;python_version>="3.8"', 'selenium==4.11.2;python_version<"3.8"', - 'selenium==4.21.0;python_version>="3.8"', + 'selenium==4.22.0;python_version>="3.8"', 'cssselect==1.2.0', "sortedcontainers==2.4.0", 'fasteners==0.19', @@ -212,6 +215,7 @@ "pdbp==1.5.0", 'colorama==0.4.6', 'pyotp==2.9.0', + 'python-xlib==0.33;platform_system=="Linux"', 'markdown-it-py==2.2.0;python_version<"3.8"', 'markdown-it-py==3.0.0;python_version>="3.8"', 'mdurl==0.1.2', @@ -230,7 +234,7 @@ # Usage: coverage run -m pytest; coverage html; coverage report "coverage": [ 'coverage==7.2.7;python_version<"3.8"', - 'coverage>=7.5.3;python_version>="3.8"', + 'coverage>=7.5.4;python_version>="3.8"', 'pytest-cov==4.1.0;python_version<"3.8"', 'pytest-cov>=5.0.0;python_version>="3.8"', ], @@ -238,12 +242,12 @@ # Usage: flake8 "flake8": [ 'flake8==5.0.4;python_version<"3.9"', - 'flake8==7.0.0;python_version>="3.9"', + 'flake8==7.1.0;python_version>="3.9"', "mccabe==0.7.0", 'pyflakes==2.5.0;python_version<"3.9"', 'pyflakes==3.2.0;python_version>="3.9"', 'pycodestyle==2.9.1;python_version<"3.9"', - 'pycodestyle==2.11.1;python_version>="3.9"', + 'pycodestyle==2.12.0;python_version>="3.9"', ], # pip install -e .[ipdb] # (Not needed for debugging anymore. SeleniumBase now includes "pdbp".) @@ -284,6 +288,10 @@ "psutil": [ "psutil==5.9.8", ], + # pip install -e .[pyautogui] + "pyautogui": [ + "PyAutoGUI==0.9.54", + ], # pip install -e .[selenium-stealth] "selenium-stealth": [ 'selenium-stealth==1.0.6', From 57a52c3fb03bcea6d0ac14ef6cf01478d178739a Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Sun, 23 Jun 2024 13:46:39 -0400 Subject: [PATCH 7/9] Update method_summary.md --- help_docs/method_summary.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/help_docs/method_summary.md b/help_docs/method_summary.md index 468d2c93ab7..d90701cbd90 100644 --- a/help_docs/method_summary.md +++ b/help_docs/method_summary.md @@ -111,7 +111,7 @@ self.click_partial_link(partial_link_text, timeout=None) # Duplicates: # self.click_partial_link_text(partial_link_text, timeout=None) -self.get_text(selector, by="css selector", timeout=None) +self.get_text(selector="html", by="css selector", timeout=None) self.get_attribute(selector, attribute, by="css selector", timeout=None, hard_fail=True) @@ -127,7 +127,7 @@ self.remove_attributes(selector, attribute, by="css selector") self.get_property(selector, property, by="css selector", timeout=None) -self.get_text_content(selector, by="css selector", timeout=None) +self.get_text_content(selector="html", by="css selector", timeout=None) self.get_property_value(selector, property, by="css selector", timeout=None) @@ -229,7 +229,7 @@ self.set_window_size(width, height) self.maximize_window() -self.switch_to_frame(frame, timeout=None) +self.switch_to_frame(frame="iframe", timeout=None) self.switch_to_default_content() @@ -1032,7 +1032,7 @@ driver.get_page_source() driver.get_title() -driver.switch_to_frame(frame) +driver.switch_to_frame(frame="iframe") ############ From 3e5a2c227a7db9e61d4404987fb8f5d59b14835e Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Sun, 23 Jun 2024 13:49:34 -0400 Subject: [PATCH 8/9] Add more examples --- examples/raw_antibot_login.py | 15 +++++++++++++++ examples/raw_block.py | 11 +++++++++++ examples/raw_brotector_captcha.py | 9 +++++++++ examples/raw_detection.py | 12 ++++++++++++ examples/raw_hobbit.py | 13 +++++++++++++ examples/raw_pyautogui.py | 19 +++++++++++++++++++ 6 files changed, 79 insertions(+) create mode 100644 examples/raw_antibot_login.py create mode 100644 examples/raw_block.py create mode 100644 examples/raw_brotector_captcha.py create mode 100644 examples/raw_detection.py create mode 100644 examples/raw_hobbit.py create mode 100644 examples/raw_pyautogui.py diff --git a/examples/raw_antibot_login.py b/examples/raw_antibot_login.py new file mode 100644 index 00000000000..0ee882616b1 --- /dev/null +++ b/examples/raw_antibot_login.py @@ -0,0 +1,15 @@ +"""UC Mode has PyAutoGUI methods for CAPTCHA-bypass.""" +from seleniumbase import SB + +with SB(uc=True, test=True) as sb: + url = "https://seleniumbase.io/antibot/login" + sb.uc_open_with_disconnect(url, 2.15) + sb.uc_gui_write("\t" + "demo_user") + sb.uc_gui_write("\t" + "secret_pass") + sb.uc_gui_press_keys("\t" + " ") # For Single-char keys + sb.sleep(1.5) + sb.uc_gui_press_keys(["\t", "ENTER"]) # Multi-char keys + sb.reconnect(1.8) + sb.assert_text("Welcome!", "h1") + sb.set_messenger_theme(location="bottom_center") + sb.post_message("SeleniumBase wasn't detected!") diff --git a/examples/raw_block.py b/examples/raw_block.py new file mode 100644 index 00000000000..bb4ffc95dd9 --- /dev/null +++ b/examples/raw_block.py @@ -0,0 +1,11 @@ +"""If Brotector catches you, Gandalf blocks you!""" +from seleniumbase import SB + +with SB(test=True) as sb: + url = "https://seleniumbase.io/hobbit/login" + sb.open(url) + sb.click_if_visible("button") + sb.assert_text("Gandalf blocked you!", "h1") + sb.click("img") + sb.highlight("h1") + sb.sleep(3) # Gandalf: "You Shall Not Pass!" diff --git a/examples/raw_brotector_captcha.py b/examples/raw_brotector_captcha.py new file mode 100644 index 00000000000..b123b29f274 --- /dev/null +++ b/examples/raw_brotector_captcha.py @@ -0,0 +1,9 @@ +"""UC Mode has PyAutoGUI methods for CAPTCHA-bypass.""" +from seleniumbase import SB + +with SB(uc=True, test=True) as sb: + url = "https://seleniumbase.io/apps/brotector" + sb.uc_open_with_disconnect(url, 2.2) + sb.uc_gui_press_key("\t") + sb.uc_gui_press_key(" ") + sb.reconnect(2.2) diff --git a/examples/raw_detection.py b/examples/raw_detection.py new file mode 100644 index 00000000000..de8d96c97c6 --- /dev/null +++ b/examples/raw_detection.py @@ -0,0 +1,12 @@ +"""The Brotector CAPTCHA in action.""" +from seleniumbase import SB + +with SB(test=True) as sb: + sb.open("https://seleniumbase.io/antibot/login") + sb.highlight("h4", loops=6) + sb.type("#username", "demo_user") + sb.type("#password", "secret_pass") + sb.click_if_visible("button span") + sb.highlight("label#pText") + sb.highlight("table#detections") + sb.sleep(4.4) # Add time to read the table diff --git a/examples/raw_hobbit.py b/examples/raw_hobbit.py new file mode 100644 index 00000000000..51fbc98dd83 --- /dev/null +++ b/examples/raw_hobbit.py @@ -0,0 +1,13 @@ +"""UC Mode has PyAutoGUI methods for CAPTCHA-bypass.""" +from seleniumbase import SB + +with SB(uc=True, test=True) as sb: + url = "https://seleniumbase.io/hobbit/login" + sb.uc_open_with_disconnect(url, 2.2) + sb.uc_gui_press_keys("\t ") + sb.reconnect(1.5) + sb.assert_text("Welcome to Middle Earth!", "h1") + sb.set_messenger_theme(location="bottom_center") + sb.post_message("SeleniumBase wasn't detected!") + sb.click("img") + sb.sleep(5.888) # Cool animation happening now! diff --git a/examples/raw_pyautogui.py b/examples/raw_pyautogui.py new file mode 100644 index 00000000000..2c06bbe6777 --- /dev/null +++ b/examples/raw_pyautogui.py @@ -0,0 +1,19 @@ +""" +UC Mode now has uc_gui_handle_cf(), which uses PyAutoGUI. +An incomplete UserAgent is used to force CAPTCHA-solving. +""" +import sys +from seleniumbase import SB + +agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/126.0.0.0" +if "linux" in sys.platform: + agent = None # Use the default UserAgent + +with SB(uc=True, test=True, rtf=True, agent=agent) as sb: + url = "https://www.virtualmanager.com/en/login" + sb.uc_open_with_reconnect(url, 4) + sb.uc_gui_handle_cf() # Ready if needed! + sb.assert_element('input[name*="email"]') + sb.assert_element('input[name*="login"]') + sb.set_messenger_theme(location="bottom_center") + sb.post_message("SeleniumBase wasn't detected!") From c1973979b34f1c3d9b7f911bc1807779b7791f40 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Sun, 23 Jun 2024 13:49:55 -0400 Subject: [PATCH 9/9] Version 4.28.0 --- seleniumbase/__version__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/seleniumbase/__version__.py b/seleniumbase/__version__.py index e06fd45c16b..bab6b9fe392 100755 --- a/seleniumbase/__version__.py +++ b/seleniumbase/__version__.py @@ -1,2 +1,2 @@ # seleniumbase package -__version__ = "4.27.5" +__version__ = "4.28.0"