From ac3a9bed3940cab1ed87adb3d4d209dfbd995c29 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Sat, 6 Jul 2024 02:40:11 -0400 Subject: [PATCH 1/6] Add UC Mode clicking methods --- seleniumbase/core/browser_launcher.py | 150 +++++++++++++++++++++++++- seleniumbase/fixtures/base_case.py | 4 + 2 files changed, 151 insertions(+), 3 deletions(-) diff --git a/seleniumbase/core/browser_launcher.py b/seleniumbase/core/browser_launcher.py index 79afe2ce8b1..687e5f0ece5 100644 --- a/seleniumbase/core/browser_launcher.py +++ b/seleniumbase/core/browser_launcher.py @@ -649,12 +649,144 @@ def uc_gui_write(driver, text): pyautogui.write(text) -def uc_gui_handle_cf(driver, frame="iframe"): +def get_gui_element_position(driver, selector): + element = driver.wait_for_element_present(selector, timeout=3) + element_rect = element.rect + window_rect = driver.get_window_rect() + window_bottom_y = window_rect["y"] + window_rect["height"] + viewport_height = driver.execute_script("return window.innerHeight;") + viewport_x = window_rect["x"] + element_rect["x"] + viewport_y = window_bottom_y - viewport_height + element_rect["y"] + return (viewport_x, viewport_y) + + +def uc_gui_click_x_y(driver, x, y, timeframe=0.25, uc_lock=True): + install_pyautogui_if_missing(driver) + import pyautogui + pyautogui = get_configured_pyautogui(pyautogui) + screen_width, screen_height = pyautogui.size() + if x > screen_width or y > screen_height: + raise Exception( + "PyAutoGUI cannot click on point (%s, %s)" + " outside screen. (Width: %s, Height: %s)" + % (x, y, screen_width, screen_height) + ) + if uc_lock: + gui_lock = fasteners.InterProcessLock( + constants.MultiBrowser.PYAUTOGUILOCK + ) + with gui_lock: # Prevent issues with multiple processes + pyautogui.moveTo(x, y, timeframe, pyautogui.easeOutQuad) + if timeframe >= 0.25: + time.sleep(0.0555) # Wait if moving at human-speed + if "--debug" in sys.argv: + print(" pyautogui.click(%s, %s)" % (x, y)) + pyautogui.click(x=x, y=y) + else: + # Called from a method where the gui_lock is already active + pyautogui.moveTo(x, y, timeframe, pyautogui.easeOutQuad) + if timeframe >= 0.25: + time.sleep(0.0555) # Wait if moving at human-speed + if "--debug" in sys.argv: + print(" pyautogui.click(%s, %s)" % (x, y)) + pyautogui.click(x=x, y=y) + + +def on_a_cf_turnstile_page(driver): source = driver.get_page_source() if ( - "//challenges.cloudflare.com" not in source - and 'aria-label="Cloudflare"' not in source + "//challenges.cloudflare.com" in source + or 'aria-label="Cloudflare"' in source ): + return True + return False + + +def uc_gui_click_cf(driver, frame="iframe", retry=False, blind=False): + if not on_a_cf_turnstile_page(driver): + return + install_pyautogui_if_missing(driver) + import pyautogui + pyautogui = get_configured_pyautogui(pyautogui) + x = None + y = None + 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, 2, uc_lock=False + ) + if not is_in_frame or needs_switch: + # Currently not in frame (or nested frame outside CF one) + try: + i_x, i_y = get_gui_element_position(driver, "iframe") + driver.switch_to_frame(frame) + except Exception: + if driver.is_element_present("iframe"): + i_x, i_y = get_gui_element_position(driver, "iframe") + driver.switch_to_frame("iframe") + else: + return + try: + selector = "span" + element = driver.wait_for_element_present(selector, timeout=2.5) + x = i_x + element.rect["x"] + int(element.rect["width"] / 2) + 1 + y = i_y + element.rect["y"] + int(element.rect["height"] / 2) + 1 + driver.switch_to.default_content() + except Exception: + try: + driver.switch_to.default_content() + except Exception: + return + driver.disconnect() + try: + if x and y: + sb_config._saved_cf_x_y = (x, y) + uc_gui_click_x_y(driver, x, y, timeframe=0.842, uc_lock=False) + except Exception: + pass + reconnect_time = (float(constants.UC.RECONNECT_TIME) / 2.0) + 0.5 + if IS_LINUX: + reconnect_time = constants.UC.RECONNECT_TIME + if not x or not y: + reconnect_time = 1 # Make it quick (it already failed) + driver.reconnect(reconnect_time) + if blind: + retry = True + if retry and x and y and on_a_cf_turnstile_page(driver): + with gui_lock: # Prevent issues with multiple processes + # Make sure the window is on top + page_actions.switch_to_window( + driver, driver.current_window_handle, 2, uc_lock=False + ) + driver.switch_to_frame("iframe") + if driver.is_element_visible("#success-icon"): + driver.switch_to.parent_frame() + return + if blind: + driver.uc_open_with_disconnect(driver.current_url, 3.8) + uc_gui_click_x_y(driver, x, y, timeframe=1.05, uc_lock=False) + else: + driver.uc_open_with_reconnect(driver.current_url, 3.8) + if on_a_cf_turnstile_page(driver): + driver.disconnect() + uc_gui_click_x_y( + driver, x, y, timeframe=1.05, uc_lock=False + ) + driver.reconnect(reconnect_time) + + +def uc_gui_handle_cf(driver, frame="iframe"): + if not on_a_cf_turnstile_page(driver): return install_pyautogui_if_missing(driver) import pyautogui @@ -696,6 +828,8 @@ def uc_gui_handle_cf(driver, frame="iframe"): except Exception: pass reconnect_time = (float(constants.UC.RECONNECT_TIME) / 2.0) + 0.5 + if IS_LINUX: + reconnect_time = constants.UC.RECONNECT_TIME driver.reconnect(reconnect_time) @@ -4027,6 +4161,16 @@ def get_local_driver( driver, *args, **kwargs ) ) + driver.uc_gui_click_x_y = ( + lambda *args, **kwargs: uc_gui_click_x_y( + driver, *args, **kwargs + ) + ) + driver.uc_gui_click_cf = ( + lambda *args, **kwargs: uc_gui_click_cf( + driver, *args, **kwargs + ) + ) driver.uc_gui_handle_cf = ( lambda *args, **kwargs: uc_gui_handle_cf( driver, *args, **kwargs diff --git a/seleniumbase/fixtures/base_case.py b/seleniumbase/fixtures/base_case.py index 1af4806f8a5..c3cfd834aa4 100644 --- a/seleniumbase/fixtures/base_case.py +++ b/seleniumbase/fixtures/base_case.py @@ -4178,6 +4178,10 @@ def get_new_driver( 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_click_x_y"): + self.uc_gui_click_x_y = new_driver.uc_gui_click_x_y + if hasattr(new_driver, "uc_gui_click_cf"): + self.uc_gui_click_cf = new_driver.uc_gui_click_cf 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"): From 4dcbf3c22f915cd4843c7a8bbed988a6708f31d7 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Sat, 6 Jul 2024 02:41:00 -0400 Subject: [PATCH 2/6] Add more methods to SB --- seleniumbase/fixtures/base_case.py | 68 ++++++++++++++++++++++++++++-- 1 file changed, 65 insertions(+), 3 deletions(-) diff --git a/seleniumbase/fixtures/base_case.py b/seleniumbase/fixtures/base_case.py index c3cfd834aa4..1ebae913109 100644 --- a/seleniumbase/fixtures/base_case.py +++ b/seleniumbase/fixtures/base_case.py @@ -3393,23 +3393,85 @@ def safe_execute_script(self, script, *args, **kwargs): self.activate_jquery() return self.driver.execute_script(script, *args, **kwargs) + def get_gui_element_rect(self, selector, by="css selector"): + """Very similar to element.rect, but the x, y coordinates are + relative to the entire screen, rather than the browser window. + This is specifically for PyAutoGUI actions on the full screen. + (Note: There may be complications if iframes are involved.)""" + element = self.wait_for_element_present(selector, by=by, timeout=1) + element_rect = element.rect + e_width = element_rect["width"] + e_height = element_rect["height"] + i_x = 0 + i_y = 0 + iframe_switch = False + if self.__is_in_frame(): + self.switch_to_parent_frame() + if self.__is_in_frame(): + raise Exception("Nested iframes breaks get_gui_element_rect!") + iframe_switch = True + iframe = self.wait_for_element_present("iframe", timeout=1) + i_x = iframe.rect["x"] + i_y = iframe.rect["y"] + window_rect = self.get_window_rect() + w_bottom_y = window_rect["y"] + window_rect["height"] + viewport_height = self.execute_script("return window.innerHeight;") + x = math.ceil(window_rect["x"] + i_x + element_rect["x"]) + y = math.ceil(w_bottom_y - viewport_height + i_y + element_rect["y"]) + if iframe_switch: + self.switch_to_frame() + if not self.is_element_present(selector, by=by): + self.switch_to_parent_frame() + return ({"height": e_height, "width": e_width, "x": x, "y": y}) + + def get_gui_element_center(self, selector, by="css selector"): + """Returns the x, y coordinates of the element's center based + on the entire GUI / screen, rather than on the browser window. + This is specifically for PyAutoGUI actions on the full screen. + (Note: There may be complications if iframes are involved.)""" + element_rect = self.get_gui_element_rect(selector, by=by) + x = int(element_rect["x"]) + int(element_rect["width"] / 2) + 1 + y = int(element_rect["y"]) + int(element_rect["height"] / 2) + 1 + return (x, y) + + def get_window_rect(self): + self.__check_scope() + self._check_browser() + return self.driver.get_window_rect() + + def get_window_size(self): + self.__check_scope() + self._check_browser() + return self.driver.get_window_size() + + def get_window_position(self): + self.__check_scope() + self._check_browser() + return self.driver.get_window_position() + def set_window_rect(self, x, y, width, height): self.__check_scope() self._check_browser() self.driver.set_window_rect(x, y, width, height) - self.__demo_mode_pause_if_active() + self.__demo_mode_pause_if_active(tiny=True) def set_window_size(self, width, height): self.__check_scope() self._check_browser() self.driver.set_window_size(width, height) - self.__demo_mode_pause_if_active() + self.__demo_mode_pause_if_active(tiny=True) + + def set_window_position(self, x, y): + self.__check_scope() + self._check_browser() + self.driver.set_window_position(x, y) + self.__demo_mode_pause_if_active(tiny=True) def maximize_window(self): self.__check_scope() self._check_browser() self.driver.maximize_window() - self.__demo_mode_pause_if_active() + self.__demo_mode_pause_if_active(tiny=True) def switch_to_frame(self, frame="iframe", timeout=None): """Wait for an iframe to appear, and switch to it. This should be From de75ec61353d0bf228f2ad22c66841dfe55da26a Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Sat, 6 Jul 2024 02:41:38 -0400 Subject: [PATCH 3/6] Update examples --- examples/raw_gui_click.py | 19 +++++++++++++++++++ examples/raw_order_tickets.py | 11 +++++++---- examples/raw_uc_mode.py | 5 ++--- 3 files changed, 28 insertions(+), 7 deletions(-) create mode 100644 examples/raw_gui_click.py diff --git a/examples/raw_gui_click.py b/examples/raw_gui_click.py new file mode 100644 index 00000000000..768da6d0bbc --- /dev/null +++ b/examples/raw_gui_click.py @@ -0,0 +1,19 @@ +""" +UC Mode now has uc_gui_click_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_click_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!") diff --git a/examples/raw_order_tickets.py b/examples/raw_order_tickets.py index 9dd64776309..43775eebd63 100644 --- a/examples/raw_order_tickets.py +++ b/examples/raw_order_tickets.py @@ -1,11 +1,14 @@ from seleniumbase import SB -with SB(uc=True, test=True, ad_block_on=True) as sb: +with SB(uc=True, test=True, ad_block=True) as sb: url = "https://www.thaiticketmajor.com/concert/" - sb.driver.uc_open_with_reconnect(url, 6.111) - sb.driver.uc_click("button.btn-signin", 4.1) + sb.uc_open_with_reconnect(url, 6.111) + sb.uc_click("button.btn-signin", 4.1) sb.switch_to_frame('iframe[title*="Cloudflare"]') - sb.assert_element("div#success svg#success-icon") + if not sb.is_element_visible("svg#success-icon"): + sb.uc_gui_handle_cf() + sb.switch_to_frame('iframe[title*="Cloudflare"]') + sb.assert_element("svg#success-icon") sb.switch_to_default_content() sb.set_messenger_theme(location="top_center") sb.post_message("SeleniumBase wasn't detected!") diff --git a/examples/raw_uc_mode.py b/examples/raw_uc_mode.py index 560cba94719..c0cd257486e 100644 --- a/examples/raw_uc_mode.py +++ b/examples/raw_uc_mode.py @@ -3,9 +3,8 @@ with SB(uc=True, test=True) as sb: url = "https://gitlab.com/users/sign_in" - sb.driver.uc_open_with_reconnect(url, 3) - if not sb.is_text_visible("Username", '[for="user_login"]'): - sb.driver.uc_open_with_reconnect(url, 4) + sb.uc_open_with_reconnect(url, 4) + sb.uc_gui_click_cf() sb.assert_text("Username", '[for="user_login"]', timeout=3) sb.assert_element('label[for="user_login"]') sb.highlight('button:contains("Sign in")') From afcd439408e15df500077a95dfabc697ed7b8c7d Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Sat, 6 Jul 2024 02:42:16 -0400 Subject: [PATCH 4/6] Update the documentation --- help_docs/method_summary.md | 16 ++++++++++++++++ help_docs/uc_mode.md | 34 +++++++++++++++++++++------------- 2 files changed, 37 insertions(+), 13 deletions(-) diff --git a/help_docs/method_summary.md b/help_docs/method_summary.md index d90701cbd90..557d913b52e 100644 --- a/help_docs/method_summary.md +++ b/help_docs/method_summary.md @@ -223,10 +223,22 @@ self.execute_async_script(script, timeout=None) self.safe_execute_script(script, *args, **kwargs) +self.get_gui_element_rect(selector, by="css selector") + +self.get_gui_element_center(selector, by="css selector") + +self.get_window_rect() + +self.get_window_size() + +self.get_window_position() + self.set_window_rect(x, y, width, height) self.set_window_size(width, height) +self.set_window_position(x, y) + self.maximize_window() self.switch_to_frame(frame="iframe", timeout=None) @@ -1062,6 +1074,10 @@ 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_click_x_y(x, y, timeframe=0.25) # PyAutoGUI click screen + +driver.uc_gui_click_cf(frame="iframe", retry=False, blind=False) # (*) + 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 b89ba282f83..e6155d0caf9 100644 --- a/help_docs/uc_mode.md +++ b/help_docs/uc_mode.md @@ -42,7 +42,7 @@ from seleniumbase import SB with SB(uc=True) as sb: url = "https://gitlab.com/users/sign_in" - sb.driver.uc_open_with_reconnect(url, 3) + sb.uc_open_with_reconnect(url, 3) ``` 👤 Here's a longer example, which includes a retry if the CAPTCHA isn't bypassed on the first attempt: @@ -52,9 +52,9 @@ from seleniumbase import SB with SB(uc=True, test=True) as sb: url = "https://gitlab.com/users/sign_in" - sb.driver.uc_open_with_reconnect(url, 3) + sb.uc_open_with_reconnect(url, 3) if not sb.is_text_visible("Username", '[for="user_login"]'): - sb.driver.uc_open_with_reconnect(url, 4) + sb.uc_open_with_reconnect(url, 4) sb.assert_text("Username", '[for="user_login"]', timeout=3) sb.highlight('label[for="user_login"]', loops=3) sb.post_message("SeleniumBase wasn't detected", duration=4) @@ -78,6 +78,8 @@ with SB(uc=True, test=True) as sb: +If running on a Linux server, `uc_gui_handle_cf()` might not be good enough. Switch to `uc_gui_click_cf()` to be more stealthy. + 👤 Here's an example where the CAPTCHA appears after submitting a form: ```python @@ -87,10 +89,10 @@ with SB(uc=True, test=True, locale_code="en") as sb: url = "https://ahrefs.com/website-authority-checker" input_field = 'input[placeholder="Enter domain"]' submit_button = 'span:contains("Check Authority")' - sb.driver.uc_open_with_reconnect(url, 1) # The bot-check is later + sb.uc_open_with_reconnect(url, 1) # The bot-check is later sb.type(input_field, "github.com/seleniumbase/SeleniumBase") - sb.driver.reconnect(0.1) - sb.driver.uc_click(submit_button, reconnect_time=4) + sb.reconnect(0.1) + sb.uc_click(submit_button, reconnect_time=4) sb.wait_for_text_not_visible("Checking", timeout=10) sb.highlight('p:contains("github.com/seleniumbase/SeleniumBase")') sb.highlight('a:contains("Top 100 backlinks")') @@ -105,10 +107,10 @@ with SB(uc=True, test=True, locale_code="en") as sb: ```python from seleniumbase import SB -with SB(uc=True, test=True, ad_block_on=True) as sb: +with SB(uc=True, test=True, ad_block=True) as sb: url = "https://www.thaiticketmajor.com/concert/" - sb.driver.uc_open_with_reconnect(url, 5.5) - sb.driver.uc_click("button.btn-signin", 4) + sb.uc_open_with_reconnect(url, 5.5) + sb.uc_click("button.btn-signin", 4) sb.switch_to_frame('iframe[title*="Cloudflare"]') sb.assert_element("div#success svg#success-icon") sb.switch_to_default_content() @@ -118,7 +120,7 @@ with SB(uc=True, test=True, ad_block_on=True) as sb: -👤 On Linux, use `sb.uc_gui_handle_cf()` to handle Cloudflare Turnstiles: +👤 On Linux, use `sb.uc_gui_click_cf()` to handle Cloudflare Turnstiles: ```python from seleniumbase import SB @@ -127,7 +129,7 @@ with SB(uc=True, test=True) as sb: url = "https://www.virtualmanager.com/en/login" sb.uc_open_with_reconnect(url, 4) print(sb.get_page_title()) - sb.uc_gui_handle_cf() # Ready if needed! + sb.uc_gui_click_cf() # Ready if needed! print(sb.get_page_title()) sb.assert_element('input[name*="email"]') sb.assert_element('input[name*="login"]') @@ -135,7 +137,7 @@ with SB(uc=True, test=True) as sb: sb.post_message("SeleniumBase wasn't detected!") ``` -uc_gui_handle_cf on Linux +uc_gui_click_cf on Linux The 2nd `print()` should output "Virtual Manager", which means that the automation successfully passed the Turnstile. @@ -188,6 +190,10 @@ driver.uc_gui_press_keys(keys) driver.uc_gui_write(text) +driver.uc_gui_click_x_y(x, y, timeframe=0.25) + +driver.uc_gui_click_cf(frame="iframe", retry=False, blind=False) + driver.uc_gui_handle_cf(frame="iframe") driver.uc_switch_to_frame(frame, reconnect_time=None) @@ -225,7 +231,9 @@ driver.reconnect("breakpoint") (Note that while the special UC Mode breakpoint is active, you can't use Selenium commands in the browser, and the browser can't detect Selenium.) -👤 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. +👤 On Linux, you may need to use `driver.uc_gui_click_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_click_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. + +👤 `driver.uc_gui_click_cf(frame="iframe", retry=False, blind=False)` has three args. (All optional). The first one, `frame`, lets you specify the iframe in case the CAPTCHA is not located in the first iframe on the page. The second one, `retry`, lets you retry the click after reloading the page if the first one didn't work (and a CAPTCHA is still present after the page reload). The third arg, `blind`, will retry after a page reload (if the first click failed) by clicking at the last known coordinates of the CAPTCHA checkbox without confirming first with Selenium that a CAPTCHA is still on the page. 👤 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: From 5a06b39dd7023c44938f92103ac40fedbcad860a Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Sat, 6 Jul 2024 02:42:43 -0400 Subject: [PATCH 5/6] Refresh Python dependencies --- mkdocs_build/requirements.txt | 2 +- requirements.txt | 6 +++--- setup.py | 10 +++++----- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/mkdocs_build/requirements.txt b/mkdocs_build/requirements.txt index 73f59a3b895..eaf0295a961 100644 --- a/mkdocs_build/requirements.txt +++ b/mkdocs_build/requirements.txt @@ -20,7 +20,7 @@ lxml==5.2.2 pyquery==2.0.0 readtime==3.0.0 mkdocs==1.6.0 -mkdocs-material==9.5.27 +mkdocs-material==9.5.28 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 9cddf0cc5c3..f9e49ca33ff 100755 --- a/requirements.txt +++ b/requirements.txt @@ -3,11 +3,11 @@ pip>=24.1.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.1.1;python_version>="3.8" +setuptools>=70.2.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 +certifi>=2024.7.4 exceptiongroup>=1.2.1 filelock>=3.12.2;python_version<"3.8" filelock>=3.15.4;python_version>="3.8" @@ -29,7 +29,7 @@ sniffio==1.3.1 h11==0.14.0 outcome==1.3.0.post0 trio==0.22.2;python_version<"3.8" -trio==0.25.1;python_version>="3.8" +trio==0.26.0;python_version>="3.8" trio-websocket==0.11.1 wsproto==1.2.0 websocket-client==1.8.0;python_version>="3.8" diff --git a/setup.py b/setup.py index 5f59c7b9976..df3621f2315 100755 --- a/setup.py +++ b/setup.py @@ -151,11 +151,11 @@ 'packaging>=24.0;python_version<"3.8"', 'packaging>=24.1;python_version>="3.8"', 'setuptools>=68.0.0;python_version<"3.8"', - 'setuptools>=70.1.1;python_version>="3.8"', + 'setuptools>=70.2.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", + "certifi>=2024.7.4", "exceptiongroup>=1.2.1", 'filelock>=3.12.2;python_version<"3.8"', 'filelock>=3.15.4;python_version>="3.8"', @@ -177,7 +177,7 @@ 'h11==0.14.0', 'outcome==1.3.0.post0', 'trio==0.22.2;python_version<"3.8"', - 'trio==0.25.1;python_version>="3.8"', + 'trio==0.26.0;python_version>="3.8"', 'trio-websocket==0.11.1', 'wsproto==1.2.0', 'websocket-client==1.8.0;python_version>="3.8"', @@ -270,7 +270,7 @@ # (An optional library for image-processing.) "pillow": [ 'Pillow==9.5.0;python_version<"3.8"', - 'Pillow>=10.3.0;python_version>="3.8"', + 'Pillow>=10.4.0;python_version>="3.8"', ], # pip install -e .[pip-system-certs] # (If you see [SSL: CERTIFICATE_VERIFY_FAILED], then get this.) @@ -282,7 +282,7 @@ # Usage: proxy # (That starts a proxy server on "127.0.0.1:8899".) "proxy": [ - "proxy.py==2.4.4", + "proxy.py==2.4.3", # 2.4.4 did not have "Listening on ..." ], # pip install -e .[psutil] "psutil": [ From 120b1ae97883ea7778ab295998dc6dc9c12c2985 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Sat, 6 Jul 2024 02:43:11 -0400 Subject: [PATCH 6/6] Version 4.28.4 --- seleniumbase/__version__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/seleniumbase/__version__.py b/seleniumbase/__version__.py index 4b67644ca06..e965f8ed76b 100755 --- a/seleniumbase/__version__.py +++ b/seleniumbase/__version__.py @@ -1,2 +1,2 @@ # seleniumbase package -__version__ = "4.28.3" +__version__ = "4.28.4"