|
1 | 1 | """Add CDP methods to extend the driver"""
|
| 2 | +import fasteners |
2 | 3 | import math
|
3 | 4 | import os
|
4 | 5 | import re
|
| 6 | +import sys |
5 | 7 | import time
|
6 | 8 | from contextlib import suppress
|
7 | 9 | from seleniumbase import config as sb_config
|
@@ -239,6 +241,9 @@ def select_all(self, selector, timeout=settings.SMALL_TIMEOUT):
|
239 | 241 | self.__slow_mode_pause_if_set()
|
240 | 242 | return updated_elements
|
241 | 243 |
|
| 244 | + def find_elements(self, selector, timeout=settings.SMALL_TIMEOUT): |
| 245 | + return self.select_all(selector, timeout=timeout) |
| 246 | + |
242 | 247 | def click_link(self, link_text):
|
243 | 248 | self.find_elements_by_text(link_text, "a")[0].click()
|
244 | 249 |
|
@@ -835,6 +840,194 @@ def set_attributes(self, selector, attribute, value):
|
835 | 840 | with suppress(Exception):
|
836 | 841 | self.loop.run_until_complete(self.page.evaluate(js_code))
|
837 | 842 |
|
| 843 | + def __verify_pyautogui_has_a_headed_browser(self): |
| 844 | + """PyAutoGUI requires a headed browser so that it can |
| 845 | + focus on the correct element when performing actions.""" |
| 846 | + driver = self.driver |
| 847 | + if hasattr(driver, "cdp_base"): |
| 848 | + driver = driver.cdp_base |
| 849 | + if driver.config.headless: |
| 850 | + raise Exception( |
| 851 | + "PyAutoGUI can't be used in headless mode!" |
| 852 | + ) |
| 853 | + |
| 854 | + def __install_pyautogui_if_missing(self): |
| 855 | + self.__verify_pyautogui_has_a_headed_browser() |
| 856 | + driver = self.driver |
| 857 | + if hasattr(driver, "cdp_base"): |
| 858 | + driver = driver.cdp_base |
| 859 | + pip_find_lock = fasteners.InterProcessLock( |
| 860 | + constants.PipInstall.FINDLOCK |
| 861 | + ) |
| 862 | + with pip_find_lock: # Prevent issues with multiple processes |
| 863 | + try: |
| 864 | + import pyautogui |
| 865 | + with suppress(Exception): |
| 866 | + use_pyautogui_ver = constants.PyAutoGUI.VER |
| 867 | + if pyautogui.__version__ != use_pyautogui_ver: |
| 868 | + del pyautogui |
| 869 | + shared_utils.pip_install( |
| 870 | + "pyautogui", version=use_pyautogui_ver |
| 871 | + ) |
| 872 | + import pyautogui |
| 873 | + except Exception: |
| 874 | + print("\nPyAutoGUI required! Installing now...") |
| 875 | + shared_utils.pip_install( |
| 876 | + "pyautogui", version=constants.PyAutoGUI.VER |
| 877 | + ) |
| 878 | + try: |
| 879 | + import pyautogui |
| 880 | + except Exception: |
| 881 | + if ( |
| 882 | + shared_utils.is_linux() |
| 883 | + and (not sb_config.headed or sb_config.xvfb) |
| 884 | + and not driver.config.headless |
| 885 | + ): |
| 886 | + from sbvirtualdisplay import Display |
| 887 | + xvfb_width = 1366 |
| 888 | + xvfb_height = 768 |
| 889 | + if ( |
| 890 | + hasattr(sb_config, "_xvfb_width") |
| 891 | + and sb_config._xvfb_width |
| 892 | + and isinstance(sb_config._xvfb_width, int) |
| 893 | + and hasattr(sb_config, "_xvfb_height") |
| 894 | + and sb_config._xvfb_height |
| 895 | + and isinstance(sb_config._xvfb_height, int) |
| 896 | + ): |
| 897 | + xvfb_width = sb_config._xvfb_width |
| 898 | + xvfb_height = sb_config._xvfb_height |
| 899 | + if xvfb_width < 1024: |
| 900 | + xvfb_width = 1024 |
| 901 | + sb_config._xvfb_width = xvfb_width |
| 902 | + if xvfb_height < 768: |
| 903 | + xvfb_height = 768 |
| 904 | + sb_config._xvfb_height = xvfb_height |
| 905 | + with suppress(Exception): |
| 906 | + xvfb_display = Display( |
| 907 | + visible=True, |
| 908 | + size=(xvfb_width, xvfb_height), |
| 909 | + backend="xvfb", |
| 910 | + use_xauth=True, |
| 911 | + ) |
| 912 | + xvfb_display.start() |
| 913 | + |
| 914 | + def __get_configured_pyautogui(self, pyautogui_copy): |
| 915 | + if ( |
| 916 | + shared_utils.is_linux() |
| 917 | + and hasattr(pyautogui_copy, "_pyautogui_x11") |
| 918 | + and "DISPLAY" in os.environ.keys() |
| 919 | + ): |
| 920 | + if ( |
| 921 | + hasattr(sb_config, "_pyautogui_x11_display") |
| 922 | + and sb_config._pyautogui_x11_display |
| 923 | + and hasattr(pyautogui_copy._pyautogui_x11, "_display") |
| 924 | + and ( |
| 925 | + sb_config._pyautogui_x11_display |
| 926 | + == pyautogui_copy._pyautogui_x11._display |
| 927 | + ) |
| 928 | + ): |
| 929 | + pass |
| 930 | + else: |
| 931 | + import Xlib.display |
| 932 | + pyautogui_copy._pyautogui_x11._display = ( |
| 933 | + Xlib.display.Display(os.environ['DISPLAY']) |
| 934 | + ) |
| 935 | + sb_config._pyautogui_x11_display = ( |
| 936 | + pyautogui_copy._pyautogui_x11._display |
| 937 | + ) |
| 938 | + return pyautogui_copy |
| 939 | + |
| 940 | + def __gui_click_x_y(self, x, y, timeframe=0.25, uc_lock=False): |
| 941 | + self.__install_pyautogui_if_missing() |
| 942 | + import pyautogui |
| 943 | + pyautogui = self.__get_configured_pyautogui(pyautogui) |
| 944 | + screen_width, screen_height = pyautogui.size() |
| 945 | + if x < 0 or y < 0 or x > screen_width or y > screen_height: |
| 946 | + raise Exception( |
| 947 | + "PyAutoGUI cannot click on point (%s, %s)" |
| 948 | + " outside screen. (Width: %s, Height: %s)" |
| 949 | + % (x, y, screen_width, screen_height) |
| 950 | + ) |
| 951 | + if uc_lock: |
| 952 | + gui_lock = fasteners.InterProcessLock( |
| 953 | + constants.MultiBrowser.PYAUTOGUILOCK |
| 954 | + ) |
| 955 | + with gui_lock: # Prevent issues with multiple processes |
| 956 | + pyautogui.moveTo(x, y, timeframe, pyautogui.easeOutQuad) |
| 957 | + if timeframe >= 0.25: |
| 958 | + time.sleep(0.056) # Wait if moving at human-speed |
| 959 | + if "--debug" in sys.argv: |
| 960 | + print(" <DEBUG> pyautogui.click(%s, %s)" % (x, y)) |
| 961 | + pyautogui.click(x=x, y=y) |
| 962 | + else: |
| 963 | + # Called from a method where the gui_lock is already active |
| 964 | + pyautogui.moveTo(x, y, timeframe, pyautogui.easeOutQuad) |
| 965 | + if timeframe >= 0.25: |
| 966 | + time.sleep(0.056) # Wait if moving at human-speed |
| 967 | + if "--debug" in sys.argv: |
| 968 | + print(" <DEBUG> pyautogui.click(%s, %s)" % (x, y)) |
| 969 | + pyautogui.click(x=x, y=y) |
| 970 | + |
| 971 | + def gui_click_x_y(self, x, y, timeframe=0.25): |
| 972 | + gui_lock = fasteners.InterProcessLock( |
| 973 | + constants.MultiBrowser.PYAUTOGUILOCK |
| 974 | + ) |
| 975 | + with gui_lock: # Prevent issues with multiple processes |
| 976 | + self.__install_pyautogui_if_missing() |
| 977 | + import pyautogui |
| 978 | + pyautogui = self.__get_configured_pyautogui(pyautogui) |
| 979 | + width_ratio = 1.0 |
| 980 | + if ( |
| 981 | + shared_utils.is_windows() |
| 982 | + and ( |
| 983 | + not hasattr(sb_config, "_saved_width_ratio") |
| 984 | + or not sb_config._saved_width_ratio |
| 985 | + ) |
| 986 | + ): |
| 987 | + window_rect = self.get_window_rect() |
| 988 | + width = window_rect["width"] |
| 989 | + height = window_rect["height"] |
| 990 | + win_x = window_rect["x"] |
| 991 | + win_y = window_rect["y"] |
| 992 | + if ( |
| 993 | + hasattr(sb_config, "_saved_width_ratio") |
| 994 | + and sb_config._saved_width_ratio |
| 995 | + ): |
| 996 | + width_ratio = sb_config._saved_width_ratio |
| 997 | + else: |
| 998 | + scr_width = pyautogui.size().width |
| 999 | + self.maximize() |
| 1000 | + win_width = self.get_window_size()["width"] |
| 1001 | + width_ratio = round(float(scr_width) / float(win_width), 2) |
| 1002 | + width_ratio += 0.01 |
| 1003 | + if width_ratio < 0.45 or width_ratio > 2.55: |
| 1004 | + width_ratio = 1.01 |
| 1005 | + sb_config._saved_width_ratio = width_ratio |
| 1006 | + self.set_window_rect(win_x, win_y, width, height) |
| 1007 | + self.bring_active_window_to_front() |
| 1008 | + elif ( |
| 1009 | + shared_utils.is_windows() |
| 1010 | + and hasattr(sb_config, "_saved_width_ratio") |
| 1011 | + and sb_config._saved_width_ratio |
| 1012 | + ): |
| 1013 | + width_ratio = sb_config._saved_width_ratio |
| 1014 | + self.bring_active_window_to_front() |
| 1015 | + if shared_utils.is_windows(): |
| 1016 | + x = x * width_ratio |
| 1017 | + y = y * width_ratio |
| 1018 | + self.__gui_click_x_y(x, y, timeframe=timeframe, uc_lock=False) |
| 1019 | + return |
| 1020 | + self.bring_active_window_to_front() |
| 1021 | + self.__gui_click_x_y(x, y, timeframe=timeframe, uc_lock=False) |
| 1022 | + |
| 1023 | + def gui_click_element(self, selector, timeframe=0.25): |
| 1024 | + self.__slow_mode_pause_if_set() |
| 1025 | + x, y = self.get_gui_element_center(selector) |
| 1026 | + self.__add_light_pause() |
| 1027 | + self.gui_click_x_y(x, y, timeframe=timeframe) |
| 1028 | + self.__slow_mode_pause_if_set() |
| 1029 | + self.loop.run_until_complete(self.page.wait()) |
| 1030 | + |
838 | 1031 | def internalize_links(self):
|
839 | 1032 | """All `target="_blank"` links become `target="_self"`.
|
840 | 1033 | This prevents those links from opening in a new tab."""
|
@@ -938,6 +1131,16 @@ def assert_exact_text(
|
938 | 1131 | % (text, element.text_all, selector)
|
939 | 1132 | )
|
940 | 1133 |
|
| 1134 | + def scroll_down(self, amount=25): |
| 1135 | + self.loop.run_until_complete( |
| 1136 | + self.page.scroll_down(amount) |
| 1137 | + ) |
| 1138 | + |
| 1139 | + def scroll_up(self, amount=25): |
| 1140 | + self.loop.run_until_complete( |
| 1141 | + self.page.scroll_up(amount) |
| 1142 | + ) |
| 1143 | + |
941 | 1144 | def save_screenshot(self, name, folder=None, selector=None):
|
942 | 1145 | filename = name
|
943 | 1146 | if folder:
|
|
0 commit comments