From f242c8df5c9f610fe93a0d03dd1cfe52f128ab18 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Fri, 1 Nov 2024 12:21:57 -0400 Subject: [PATCH 1/6] Update CDP Mode --- examples/cdp_mode/ReadMe.md | 5 + seleniumbase/core/browser_launcher.py | 6 +- seleniumbase/core/sb_cdp.py | 203 ++++++++++++++++++ .../undetected/cdp_driver/cdp_util.py | 125 ++++++++++- 4 files changed, 337 insertions(+), 2 deletions(-) diff --git a/examples/cdp_mode/ReadMe.md b/examples/cdp_mode/ReadMe.md index bf6ca1ea82b..bfba01be45f 100644 --- a/examples/cdp_mode/ReadMe.md +++ b/examples/cdp_mode/ReadMe.md @@ -231,6 +231,7 @@ sb.cdp.find_all(selector) sb.cdp.find_elements_by_text(text, tag_name=None) sb.cdp.select(selector) sb.cdp.select_all(selector) +sb.cdp.find_elements(selector) sb.cdp.click_link(link_text) sb.cdp.tile_windows(windows=None, max_columns=0) sb.cdp.get_all_cookies(*args, **kwargs) @@ -290,6 +291,8 @@ sb.cdp.get_element_attributes(selector) sb.cdp.get_element_html(selector) sb.cdp.set_locale(locale) sb.cdp.set_attributes(selector, attribute, value) +sb.cdp.gui_click_x_y(x, y) +sb.cdp.gui_click_element(selector) sb.cdp.internalize_links() sb.cdp.is_element_present(selector) sb.cdp.is_element_visible(selector) @@ -297,6 +300,8 @@ sb.cdp.assert_element(selector) sb.cdp.assert_element_present(selector) sb.cdp.assert_text(text, selector="html") sb.cdp.assert_exact_text(text, selector="html") +sb.cdp.scroll_down(amount=25) +sb.cdp.scroll_up(amount=25) sb.cdp.save_screenshot(name, folder=None, selector=None) ``` diff --git a/seleniumbase/core/browser_launcher.py b/seleniumbase/core/browser_launcher.py index 8acd0499e0d..1060016c7a0 100644 --- a/seleniumbase/core/browser_launcher.py +++ b/seleniumbase/core/browser_launcher.py @@ -588,6 +588,7 @@ def uc_open_with_cdp_mode(driver, url=None): cdp.find_elements_by_text = CDPM.find_elements_by_text cdp.select = CDPM.select cdp.select_all = CDPM.select_all + cdp.find_elements = CDPM.find_elements cdp.click_link = CDPM.click_link cdp.tile_windows = CDPM.tile_windows cdp.get_all_cookies = CDPM.get_all_cookies @@ -619,6 +620,8 @@ def uc_open_with_cdp_mode(driver, url=None): cdp.reset_window_size = CDPM.reset_window_size cdp.set_locale = CDPM.set_locale cdp.set_attributes = CDPM.set_attributes + cdp.gui_click_x_y = CDPM.gui_click_x_y + cdp.gui_click_element = CDPM.gui_click_element cdp.internalize_links = CDPM.internalize_links cdp.get_window = CDPM.get_window cdp.get_element_attributes = CDPM.get_element_attributes @@ -655,6 +658,8 @@ def uc_open_with_cdp_mode(driver, url=None): cdp.assert_element_visible = CDPM.assert_element cdp.assert_text = CDPM.assert_text cdp.assert_exact_text = CDPM.assert_exact_text + cdp.scroll_down = CDPM.scroll_down + cdp.scroll_up = CDPM.scroll_up cdp.save_screenshot = CDPM.save_screenshot cdp.page = page # async world cdp.driver = driver.cdp_base # async world @@ -2218,7 +2223,6 @@ def _set_chrome_options( ) ): chrome_options.add_argument("--no-pings") - chrome_options.add_argument("--disable-popup-blocking") chrome_options.add_argument("--homepage=chrome://version/") chrome_options.add_argument("--animation-duration-scale=0") chrome_options.add_argument("--wm-window-animations-disabled") diff --git a/seleniumbase/core/sb_cdp.py b/seleniumbase/core/sb_cdp.py index 7686fd04d3f..f920764d9f7 100644 --- a/seleniumbase/core/sb_cdp.py +++ b/seleniumbase/core/sb_cdp.py @@ -1,7 +1,9 @@ """Add CDP methods to extend the driver""" +import fasteners import math import os import re +import sys import time from contextlib import suppress from seleniumbase import config as sb_config @@ -239,6 +241,9 @@ def select_all(self, selector, timeout=settings.SMALL_TIMEOUT): self.__slow_mode_pause_if_set() return updated_elements + def find_elements(self, selector, timeout=settings.SMALL_TIMEOUT): + return self.select_all(selector, timeout=timeout) + def click_link(self, link_text): self.find_elements_by_text(link_text, "a")[0].click() @@ -835,6 +840,194 @@ def set_attributes(self, selector, attribute, value): with suppress(Exception): self.loop.run_until_complete(self.page.evaluate(js_code)) + def __verify_pyautogui_has_a_headed_browser(self): + """PyAutoGUI requires a headed browser so that it can + focus on the correct element when performing actions.""" + driver = self.driver + if hasattr(driver, "cdp_base"): + driver = driver.cdp_base + if driver.config.headless: + raise Exception( + "PyAutoGUI can't be used in headless mode!" + ) + + def __install_pyautogui_if_missing(self): + self.__verify_pyautogui_has_a_headed_browser() + driver = self.driver + if hasattr(driver, "cdp_base"): + driver = driver.cdp_base + pip_find_lock = fasteners.InterProcessLock( + constants.PipInstall.FINDLOCK + ) + with pip_find_lock: # Prevent issues with multiple processes + try: + import pyautogui + with suppress(Exception): + 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: + print("\nPyAutoGUI required! Installing now...") + shared_utils.pip_install( + "pyautogui", version=constants.PyAutoGUI.VER + ) + try: + import pyautogui + except Exception: + if ( + shared_utils.is_linux() + and (not sb_config.headed or sb_config.xvfb) + and not driver.config.headless + ): + from sbvirtualdisplay import Display + xvfb_width = 1366 + xvfb_height = 768 + if ( + hasattr(sb_config, "_xvfb_width") + and sb_config._xvfb_width + and isinstance(sb_config._xvfb_width, int) + and hasattr(sb_config, "_xvfb_height") + and sb_config._xvfb_height + and isinstance(sb_config._xvfb_height, int) + ): + xvfb_width = sb_config._xvfb_width + xvfb_height = sb_config._xvfb_height + if xvfb_width < 1024: + xvfb_width = 1024 + sb_config._xvfb_width = xvfb_width + if xvfb_height < 768: + xvfb_height = 768 + sb_config._xvfb_height = xvfb_height + with suppress(Exception): + xvfb_display = Display( + visible=True, + size=(xvfb_width, xvfb_height), + backend="xvfb", + use_xauth=True, + ) + xvfb_display.start() + + def __get_configured_pyautogui(self, pyautogui_copy): + if ( + shared_utils.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 __gui_click_x_y(self, x, y, timeframe=0.25, uc_lock=False): + self.__install_pyautogui_if_missing() + import pyautogui + pyautogui = self.__get_configured_pyautogui(pyautogui) + screen_width, screen_height = pyautogui.size() + if x < 0 or y < 0 or 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.056) # 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.056) # 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 gui_click_x_y(self, x, y, timeframe=0.25): + gui_lock = fasteners.InterProcessLock( + constants.MultiBrowser.PYAUTOGUILOCK + ) + with gui_lock: # Prevent issues with multiple processes + self.__install_pyautogui_if_missing() + import pyautogui + pyautogui = self.__get_configured_pyautogui(pyautogui) + width_ratio = 1.0 + if ( + shared_utils.is_windows() + and ( + not hasattr(sb_config, "_saved_width_ratio") + or not sb_config._saved_width_ratio + ) + ): + window_rect = self.get_window_rect() + width = window_rect["width"] + height = window_rect["height"] + win_x = window_rect["x"] + win_y = window_rect["y"] + if ( + hasattr(sb_config, "_saved_width_ratio") + and sb_config._saved_width_ratio + ): + width_ratio = sb_config._saved_width_ratio + else: + scr_width = pyautogui.size().width + self.maximize() + win_width = self.get_window_size()["width"] + width_ratio = round(float(scr_width) / float(win_width), 2) + width_ratio += 0.01 + if width_ratio < 0.45 or width_ratio > 2.55: + width_ratio = 1.01 + sb_config._saved_width_ratio = width_ratio + self.set_window_rect(win_x, win_y, width, height) + self.bring_active_window_to_front() + elif ( + shared_utils.is_windows() + and hasattr(sb_config, "_saved_width_ratio") + and sb_config._saved_width_ratio + ): + width_ratio = sb_config._saved_width_ratio + self.bring_active_window_to_front() + if shared_utils.is_windows(): + x = x * width_ratio + y = y * width_ratio + self.__gui_click_x_y(x, y, timeframe=timeframe, uc_lock=False) + return + self.bring_active_window_to_front() + self.__gui_click_x_y(x, y, timeframe=timeframe, uc_lock=False) + + def gui_click_element(self, selector, timeframe=0.25): + self.__slow_mode_pause_if_set() + x, y = self.get_gui_element_center(selector) + self.__add_light_pause() + self.gui_click_x_y(x, y, timeframe=timeframe) + self.__slow_mode_pause_if_set() + self.loop.run_until_complete(self.page.wait()) + def internalize_links(self): """All `target="_blank"` links become `target="_self"`. This prevents those links from opening in a new tab.""" @@ -938,6 +1131,16 @@ def assert_exact_text( % (text, element.text_all, selector) ) + def scroll_down(self, amount=25): + self.loop.run_until_complete( + self.page.scroll_down(amount) + ) + + def scroll_up(self, amount=25): + self.loop.run_until_complete( + self.page.scroll_up(amount) + ) + def save_screenshot(self, name, folder=None, selector=None): filename = name if folder: diff --git a/seleniumbase/undetected/cdp_driver/cdp_util.py b/seleniumbase/undetected/cdp_driver/cdp_util.py index afa65b6b528..5eaa63de93f 100644 --- a/seleniumbase/undetected/cdp_driver/cdp_util.py +++ b/seleniumbase/undetected/cdp_driver/cdp_util.py @@ -1,10 +1,16 @@ """CDP-Driver is based on NoDriver""" from __future__ import annotations import asyncio +import fasteners import logging +import os import time import types import typing +from contextlib import suppress +from seleniumbase import config as sb_config +from seleniumbase.config import settings +from seleniumbase.fixtures import constants from seleniumbase.fixtures import shared_utils from typing import Optional, List, Union, Callable from .element import Element @@ -15,9 +21,120 @@ import mycdp as cdp logger = logging.getLogger(__name__) +IS_LINUX = shared_utils.is_linux() T = typing.TypeVar("T") +def __activate_standard_virtual_display(): + from sbvirtualdisplay import Display + width = settings.HEADLESS_START_WIDTH + height = settings.HEADLESS_START_HEIGHT + with suppress(Exception): + _xvfb_display = Display( + visible=0, size=(width, height) + ) + _xvfb_display.start() + sb_config._virtual_display = _xvfb_display + sb_config.headless_active = True + + +def __activate_virtual_display_as_needed( + headless, headed, xvfb, xvfb_metrics +): + """This is only needed on Linux.""" + if IS_LINUX and (not headed or xvfb): + from sbvirtualdisplay import Display + pip_find_lock = fasteners.InterProcessLock( + constants.PipInstall.FINDLOCK + ) + with pip_find_lock: # Prevent issues with multiple processes + if not headless: + import Xlib.display + try: + _xvfb_width = None + _xvfb_height = None + if xvfb_metrics: + with suppress(Exception): + metrics_string = xvfb_metrics + metrics_string = metrics_string.replace(" ", "") + metrics_list = metrics_string.split(",")[0:2] + _xvfb_width = int(metrics_list[0]) + _xvfb_height = int(metrics_list[1]) + # The minimum width,height is: 1024,768 + if _xvfb_width < 1024: + _xvfb_width = 1024 + sb_config._xvfb_width = _xvfb_width + if _xvfb_height < 768: + _xvfb_height = 768 + sb_config._xvfb_height = _xvfb_height + xvfb = True + if not _xvfb_width: + _xvfb_width = 1366 + if not _xvfb_height: + _xvfb_height = 768 + _xvfb_display = Display( + visible=True, + size=(_xvfb_width, _xvfb_height), + backend="xvfb", + use_xauth=True, + ) + _xvfb_display.start() + if "DISPLAY" not in os.environ.keys(): + print( + "\nX11 display failed! Will use regular xvfb!" + ) + __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!") + __activate_standard_virtual_display() + return + pyautogui_is_installed = False + try: + import pyautogui + with suppress(Exception): + 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 + 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: + __activate_standard_virtual_display() + + async def start( config: Optional[Config] = None, *, @@ -27,11 +144,14 @@ async def start( guest: Optional[bool] = False, browser_executable_path: Optional[PathLike] = None, browser_args: Optional[List[str]] = None, + xvfb_metrics: Optional[List[str]] = None, # "Width,Height" for Linux sandbox: Optional[bool] = True, lang: Optional[str] = None, host: Optional[str] = None, port: Optional[int] = None, - expert: Optional[bool] = None, + xvfb: Optional[int] = None, # Use a special virtual display on Linux + headed: Optional[bool] = None, # Override default Xvfb mode on Linux + expert: Optional[bool] = None, # Open up closed Shadow-root elements **kwargs: Optional[dict], ) -> Browser: """ @@ -73,6 +193,9 @@ async def start( (For example, ensuring shadow-root is always in "open" mode.) :type expert: bool """ + if IS_LINUX and not headless and not headed and not xvfb: + xvfb = True # The default setting on Linux + __activate_virtual_display_as_needed(headless, headed, xvfb, xvfb_metrics) if not config: config = Config( user_data_dir, From e56ec82f268c1b1ab7b5323d0fa6e50e1bc3db25 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Fri, 1 Nov 2024 12:22:54 -0400 Subject: [PATCH 2/6] Add new CDP Mode examples --- examples/cdp_mode/raw_easyjet.py | 42 ++++++++++++++++++++++++++++++++ examples/cdp_mode/raw_res_sb.py | 34 ++++++++++++++++++++++++++ examples/cdp_mode/raw_walmart.py | 31 +++++++++++++++++++++++ 3 files changed, 107 insertions(+) create mode 100644 examples/cdp_mode/raw_easyjet.py create mode 100644 examples/cdp_mode/raw_res_sb.py create mode 100644 examples/cdp_mode/raw_walmart.py diff --git a/examples/cdp_mode/raw_easyjet.py b/examples/cdp_mode/raw_easyjet.py new file mode 100644 index 00000000000..35ef468d171 --- /dev/null +++ b/examples/cdp_mode/raw_easyjet.py @@ -0,0 +1,42 @@ +from seleniumbase import SB + +with SB(uc=True, test=True, locale_code="en") as sb: + url = "https://www.easyjet.com/en/" + sb.activate_cdp_mode(url) + sb.sleep(2.5) + sb.cdp.click_if_visible('button#ensRejectAll') + sb.sleep(1.2) + sb.cdp.click('input[name="from"]') + sb.sleep(1.2) + sb.cdp.type('input[name="from"]', "London") + sb.sleep(1.2) + sb.cdp.click('span[data-testid="airport-name"]') + sb.sleep(1.2) + sb.cdp.type('input[name="to"]', "Venice") + sb.sleep(1.2) + sb.cdp.click('span[data-testid="airport-name"]') + sb.sleep(1.2) + sb.cdp.click('input[name="when"]') + sb.sleep(1.2) + sb.cdp.click('[data-testid="month"] button[aria-disabled="false"]') + sb.sleep(1.2) + sb.cdp.click('[data-testid="month"]:last-of-type [aria-disabled="false"]') + sb.sleep(1.2) + sb.cdp.click('button[data-testid="submit"]') + sb.sleep(3.5) + sb.connect() + sb.sleep(0.5) + if "easyjet.com" not in sb.get_current_url(): + sb.driver.close() + sb.switch_to_newest_window() + days = sb.find_elements("div.flight-grid-day") + for day in days: + print("**** " + " ".join(day.text.split("\n")[0:2]) + " ****") + fares = day.find_elements("css selector", "button.selectable") + if not fares: + print("No flights today!") + for fare in fares: + info = fare.text + info = info.replace("LOWEST FARE\n", "") + info = info.replace("\n", " ") + print(info) diff --git a/examples/cdp_mode/raw_res_sb.py b/examples/cdp_mode/raw_res_sb.py new file mode 100644 index 00000000000..e2583f320bd --- /dev/null +++ b/examples/cdp_mode/raw_res_sb.py @@ -0,0 +1,34 @@ +"""Using CDP.network.ResponseReceived and CDP.network.RequestWillBeSent.""" +import colorama +import mycdp +import sys +from seleniumbase import SB + +c1 = colorama.Fore.BLUE + colorama.Back.LIGHTYELLOW_EX +c2 = colorama.Fore.BLUE + colorama.Back.LIGHTGREEN_EX +cr = colorama.Style.RESET_ALL +if "linux" in sys.platform: + c1 = c2 = cr = "" + + +async def send_handler(event: mycdp.network.RequestWillBeSent): + r = event.request + s = f"{r.method} {r.url}" + for k, v in r.headers.items(): + s += f"\n\t{k} : {v}" + print(c1 + "*** ==> RequestWillBeSent <== ***" + cr) + print(s) + + +async def receive_handler(event: mycdp.network.ResponseReceived): + print(c2 + "*** ==> ResponseReceived <== ***" + cr) + print(event.response) + + +with SB(uc=True, test=True, locale_code="en") as sb: + sb.activate_cdp_mode("about:blank") + sb.cdp.add_handler(mycdp.network.RequestWillBeSent, send_handler) + sb.cdp.add_handler(mycdp.network.ResponseReceived, receive_handler) + url = "https://seleniumbase.io/apps/calculator" + sb.cdp.open(url) + sb.sleep(1) diff --git a/examples/cdp_mode/raw_walmart.py b/examples/cdp_mode/raw_walmart.py new file mode 100644 index 00000000000..31d704b09ca --- /dev/null +++ b/examples/cdp_mode/raw_walmart.py @@ -0,0 +1,31 @@ +from seleniumbase import SB + +with SB(uc=True, test=True, locale_code="en") as sb: + url = "https://www.walmart.com/" + sb.activate_cdp_mode(url) + sb.sleep(2.5) + sb.cdp.mouse_click('input[aria-label="Search"]') + sb.sleep(1.2) + search = "Settlers of Catan Board Game" + required_text = "Catan" + sb.cdp.press_keys('input[aria-label="Search"]', search + "\n") + sb.sleep(3.8) + items = sb.cdp.find_elements('div[data-testid="list-view"]') + print('*** Walmart Search for "%s":' % search) + print(' (Results must contain "%s".)' % required_text) + for item in items: + if required_text in item.text: + description = item.querySelector( + '[data-automation-id="product-price"] + span' + ) + if description: + print("* " + description.text) + price = item.querySelector( + '[data-automation-id="product-price"]' + ) + if price: + price_text = price.text + price_text = price_text.split("current price Now ")[-1] + price_text = price_text.split("current price ")[-1] + price_text = price_text.split(" ")[0] + print(" (" + price_text + ")") From fd33cad4cf3060e06eefdcab44b94bb4406786dc Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Fri, 1 Nov 2024 12:23:38 -0400 Subject: [PATCH 3/6] Update examples --- examples/cdp_mode/raw_req_async.py | 2 +- examples/cdp_mode/raw_req_sb.py | 2 +- examples/raw_pixelscan.py | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/cdp_mode/raw_req_async.py b/examples/cdp_mode/raw_req_async.py index 1f5abffacc4..56e331b7ef1 100644 --- a/examples/cdp_mode/raw_req_async.py +++ b/examples/cdp_mode/raw_req_async.py @@ -1,4 +1,4 @@ -"""Using CDP.fetch.RequestPaused to filter content in real time.""" +"""Using CDP.fetch.RequestPaused to filter content in real-time.""" import asyncio import mycdp from seleniumbase import decorators diff --git a/examples/cdp_mode/raw_req_sb.py b/examples/cdp_mode/raw_req_sb.py index 0de2ceae450..241b5668683 100644 --- a/examples/cdp_mode/raw_req_sb.py +++ b/examples/cdp_mode/raw_req_sb.py @@ -1,4 +1,4 @@ -"""Using CDP.fetch.RequestPaused to filter content in real time.""" +"""Using CDP.fetch.RequestPaused to filter content in real-time.""" import mycdp from seleniumbase import SB diff --git a/examples/raw_pixelscan.py b/examples/raw_pixelscan.py index 03a140019f6..6c4559c4016 100644 --- a/examples/raw_pixelscan.py +++ b/examples/raw_pixelscan.py @@ -2,6 +2,7 @@ with SB(uc=True, incognito=True, test=True) as sb: sb.driver.uc_open_with_reconnect("https://pixelscan.net/", 10) + sb.remove_elements("div.banner") # Remove the banner sb.remove_elements("jdiv") # Remove chat widgets no_automation_detected = "No automation framework detected" sb.assert_text(no_automation_detected, "pxlscn-bot-detection") From 8ae549b6daca5073f9034a6fc3000ddeb30070d0 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Fri, 1 Nov 2024 12:24:02 -0400 Subject: [PATCH 4/6] Update the docs --- help_docs/customizing_test_runs.md | 4 ++-- mkdocs_build/requirements.txt | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/help_docs/customizing_test_runs.md b/help_docs/customizing_test_runs.md index 4eb991d641a..9045f9cc3d5 100644 --- a/help_docs/customizing_test_runs.md +++ b/help_docs/customizing_test_runs.md @@ -238,7 +238,7 @@ pytest test_suite.py

Demo Mode:

-🔵 If any test is moving too fast for your eyes to see what's going on, you can run it in **Demo Mode** by adding ``--demo`` on the command line, which pauses the browser briefly between actions, highlights page elements being acted on, and lets you know what test assertions are happening in real time: +🔵 If any test is moving too fast for your eyes to see what's going on, you can run it in **Demo Mode** by adding ``--demo`` on the command line, which pauses the browser briefly between actions, highlights page elements being acted on, and lets you know what test assertions are happening in real-time: ```bash pytest my_first_test.py --demo @@ -335,7 +335,7 @@ class Test: pytest --headless -n8 --dashboard --html=report.html -v --rs --crumbs ``` -The above not only runs tests in parallel processes, but it also tells tests in the same process to share the same browser session, runs the tests in headless mode, displays the full name of each test on a separate line, creates a realtime dashboard of the test results, and creates a full report after all tests complete. +The above not only runs tests in parallel processes, but it also tells tests in the same process to share the same browser session, runs the tests in headless mode, displays the full name of each test on a separate line, creates a real-time dashboard of the test results, and creates a full report after all tests complete. -------- diff --git a/mkdocs_build/requirements.txt b/mkdocs_build/requirements.txt index 3260df8b31b..771a9a67756 100644 --- a/mkdocs_build/requirements.txt +++ b/mkdocs_build/requirements.txt @@ -1,8 +1,8 @@ # mkdocs dependencies for generating the seleniumbase.io website -# Minimum Python version: 3.8 (for generating docs only) +# Minimum Python version: 3.9 (for generating docs only) regex>=2024.9.11 -pymdown-extensions>=10.11.2 +pymdown-extensions>=10.12 pipdeptree>=2.23.4 python-dateutil>=2.8.2 Markdown==3.7 @@ -11,7 +11,7 @@ MarkupSafe==3.0.2 Jinja2==3.1.4 click==8.1.7 ghp-import==2.1.0 -watchdog==5.0.3 +watchdog==6.0.0 cairocffi==1.7.1 pathspec==0.12.1 Babel==2.16.0 @@ -20,7 +20,7 @@ lxml==5.3.0 pyquery==2.0.1 readtime==3.0.0 mkdocs==1.6.1 -mkdocs-material==9.5.42 +mkdocs-material==9.5.43 mkdocs-exclude-search==0.6.6 mkdocs-simple-hooks==0.1.5 mkdocs-material-extensions==1.3.1 From 3a76c197731e1a6676cc10954a06876e1045bdf5 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Fri, 1 Nov 2024 12:24:19 -0400 Subject: [PATCH 5/6] Refresh Python dependencies --- requirements.txt | 5 +++-- setup.py | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/requirements.txt b/requirements.txt index ffc8921fa55..bc3ffee5f27 100755 --- a/requirements.txt +++ b/requirements.txt @@ -36,7 +36,7 @@ trio==0.27.0 trio-websocket==0.11.1 wsproto==1.2.0 websocket-client==1.8.0 -selenium==4.25.0 +selenium==4.26.1 cssselect==1.2.0 sortedcontainers==2.4.0 execnet==2.1.1 @@ -64,7 +64,8 @@ rich==13.9.3 coverage>=7.6.1;python_version<"3.9" coverage>=7.6.4;python_version>="3.9" -pytest-cov>=5.0.0 +pytest-cov>=5.0.0;python_version<"3.9" +pytest-cov>=6.0.0;python_version>="3.9" flake8==5.0.4;python_version<"3.9" flake8==7.1.1;python_version>="3.9" mccabe==0.7.0 diff --git a/setup.py b/setup.py index 165e32079a9..b26122db586 100755 --- a/setup.py +++ b/setup.py @@ -185,7 +185,7 @@ 'trio-websocket==0.11.1', 'wsproto==1.2.0', 'websocket-client==1.8.0', - 'selenium==4.25.0', + 'selenium==4.26.1', 'cssselect==1.2.0', "sortedcontainers==2.4.0", 'execnet==2.1.1', @@ -222,7 +222,8 @@ "coverage": [ 'coverage>=7.6.1;python_version<"3.9"', 'coverage>=7.6.4;python_version>="3.9"', - 'pytest-cov>=5.0.0', + 'pytest-cov>=5.0.0;python_version<"3.9"', + 'pytest-cov>=6.0.0;python_version>="3.9"', ], # pip install -e .[flake8] # Usage: flake8 From ab3198a61aaec5aeb8c336d7fbda049d109967c0 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Fri, 1 Nov 2024 12:25:02 -0400 Subject: [PATCH 6/6] Version 4.32.6 --- seleniumbase/__version__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/seleniumbase/__version__.py b/seleniumbase/__version__.py index 059aca1510b..884e2a6ef40 100755 --- a/seleniumbase/__version__.py +++ b/seleniumbase/__version__.py @@ -1,2 +1,2 @@ # seleniumbase package -__version__ = "4.32.5" +__version__ = "4.32.6"