availcheck.selenium
1# -*- coding: utf-8 -*- 2# Copyright (C) 2024 TU Dresden 3# ralf.klammer@tu-dresden.de 4 5import logging 6import pathlib 7 8from selenium import webdriver 9from selenium.common import exceptions as selenium_exceptions 10from selenium.webdriver.common.by import By 11from selenium.webdriver.support import expected_conditions as EC 12from selenium.webdriver.support.ui import WebDriverWait 13 14log = logging.getLogger(__name__) 15 16DEBUG_HIGHLIGHT = "#" * 10 17 18 19class SeleniumAgent(object): 20 """ 21 A simple wrapper around the selenium webdriver to make it easier to use 22 in the context of the availability checker. 23 """ 24 25 def __init__( 26 self, 27 path_of_screenshots=None, 28 header_arguments=[], 29 implicit_wait=None, 30 browser_dir=None, 31 user_agent=""" 32 Mozilla/5.0 (X11; Linux x86_64) 33 AppleWebKit/537.36 (KHTML, like Gecko) 34 Chrome/130.0.0.0 Safari/537.36 35 """, 36 window_size=(1920, 1080), 37 ): 38 # set up the chrome options 39 chrome_options = webdriver.ChromeOptions() 40 chrome_options.add_argument("--headless") 41 chrome_options.add_argument("--no-sandbox") 42 chrome_options.add_argument("--disable-dev-shm-usage") 43 if browser_dir is not None: 44 chrome_options.add_argument(f"--user-data-dir={browser_dir}") 45 if user_agent: 46 chrome_options.add_argument("user-agent=%s" % user_agent) 47 48 for argument in header_arguments: 49 log.info("Add additional header arguments: %s" % argument) 50 chrome_options.add_argument(argument) 51 52 self.path_of_screenshots = path_of_screenshots 53 self.browser = webdriver.Chrome(chrome_options) 54 self.browser.set_window_size(*window_size) 55 56 # we need to add a script to the browser to make it work with shadow 57 # dom which is used by React.js 58 # see: https://stackoverflow.com/a/77243136 59 self.browser.execute_cdp_cmd( 60 "Page.addScriptToEvaluateOnNewDocument", 61 { 62 "source": """ 63 Element.prototype._attachShadow = Element.prototype.attachShadow; 64 Element.prototype.attachShadow = function () { 65 return this._attachShadow( { mode: "open" } ); 66 }; 67 """ 68 }, 69 ) 70 self.implicit_wait = implicit_wait 71 72 @property 73 def implicit_wait(self): 74 return self._implicit_wait 75 76 @implicit_wait.setter 77 def implicit_wait(self, value): 78 self._implicit_wait = value 79 if value is not None: 80 self.browser.implicitly_wait(value) 81 82 def get_shadow_root(self, shadow_root_css): 83 # get the shadow root of the element, this is necessary for 84 # elements that are hidden in the shadow dom 85 # see: https://stackoverflow.com/a/77243136 86 log.debug( 87 f"{DEBUG_HIGHLIGHT}get_shadow_root: {shadow_root_css}{DEBUG_HIGHLIGHT}" 88 ) 89 closed_shadow_host = self.find_by_css(shadow_root_css) 90 log.debug(f"closed_shadow_host: {closed_shadow_host}") 91 shadow_root = self.browser.execute_script( 92 "return arguments[0].shadowRoot", closed_shadow_host 93 ) 94 log.debug(f"shadow_root: {shadow_root}") 95 return shadow_root 96 97 def get(self, url): 98 self.browser.get(url) 99 100 def find_by_css(self, css=None, elem=None, as_list=False): 101 if css is None: 102 return None 103 if elem is None: 104 elem = self.browser 105 try: 106 if as_list: 107 return elem.find_elements(By.CSS_SELECTOR, css) 108 else: 109 return elem.find_element(By.CSS_SELECTOR, css) 110 except selenium_exceptions.WebDriverException as e: 111 log.info( 112 f"Unable to to find element: {css}", 113 ) 114 log.debug(e) 115 116 def find_by_text( 117 self, 118 text=None, 119 css=None, 120 elem=None, 121 as_list=False, 122 case_sensitive=False, 123 ): 124 if all([text, css]): 125 if elem is None: 126 elem = self.browser 127 result_as_list = [] 128 for item in self.find_by_css(css, elem=elem, as_list=True): 129 # convert to lower case if case insensitive is requested 130 item_text = item.text if case_sensitive else item.text.lower() 131 text = text if case_sensitive else text.lower() 132 if item_text == text: 133 if as_list: 134 result_as_list.append(item) 135 else: 136 return item 137 138 def wait_until(self, css=None, seconds=10, elem=None, want_bool=False): 139 if css is not None: 140 if elem is None: 141 elem = self.browser 142 try: 143 WebDriverWait(elem, seconds).until( 144 EC.presence_of_element_located((By.CSS_SELECTOR, css)) 145 ) 146 if want_bool: 147 return True 148 else: 149 return self.find_by_css(css=css, elem=elem) 150 except selenium_exceptions.TimeoutException: 151 log.info(f"Element not found: {css}") 152 return False 153 154 def save_screenshot(self, filename): 155 if self.path_of_screenshots: 156 self.path_of_screenshots = self.path_of_screenshots.rstrip("/") 157 filename = filename.lstrip("/") 158 filename = f"{self.path_of_screenshots}/{filename}" 159 160 if not pathlib.Path(filename).parent.exists(): 161 log.warning( 162 f"Destination for screenshots ({pathlib.Path(filename).parent}) does not exist." 163 ) 164 self.browser.save_screenshot(filename) 165 166 def zoom(self): 167 self.browser.execute_script("document.body.style.zoom='25%'") 168 169 def get_url(self): 170 current_url = self.browser.current_url 171 return current_url 172 173 def amount_of_elements(self, css=None, minimum=10): 174 if css is not None: 175 try: 176 count = self.browser.execute_script( 177 f'return document.querySelectorAll("{css}").length' 178 ) 179 if count >= minimum: 180 return True 181 else: 182 return False 183 except Exception as e: 184 log.error(f"Unable to find Element: {css}") 185 log.error(e) 186 return False 187 else: 188 log.error("CSS selector is None") 189 return False 190 191 def quit(self): 192 self.browser.quit()
log =
<Logger availcheck.selenium (WARNING)>
DEBUG_HIGHLIGHT =
'##########'
class
SeleniumAgent:
20class SeleniumAgent(object): 21 """ 22 A simple wrapper around the selenium webdriver to make it easier to use 23 in the context of the availability checker. 24 """ 25 26 def __init__( 27 self, 28 path_of_screenshots=None, 29 header_arguments=[], 30 implicit_wait=None, 31 browser_dir=None, 32 user_agent=""" 33 Mozilla/5.0 (X11; Linux x86_64) 34 AppleWebKit/537.36 (KHTML, like Gecko) 35 Chrome/130.0.0.0 Safari/537.36 36 """, 37 window_size=(1920, 1080), 38 ): 39 # set up the chrome options 40 chrome_options = webdriver.ChromeOptions() 41 chrome_options.add_argument("--headless") 42 chrome_options.add_argument("--no-sandbox") 43 chrome_options.add_argument("--disable-dev-shm-usage") 44 if browser_dir is not None: 45 chrome_options.add_argument(f"--user-data-dir={browser_dir}") 46 if user_agent: 47 chrome_options.add_argument("user-agent=%s" % user_agent) 48 49 for argument in header_arguments: 50 log.info("Add additional header arguments: %s" % argument) 51 chrome_options.add_argument(argument) 52 53 self.path_of_screenshots = path_of_screenshots 54 self.browser = webdriver.Chrome(chrome_options) 55 self.browser.set_window_size(*window_size) 56 57 # we need to add a script to the browser to make it work with shadow 58 # dom which is used by React.js 59 # see: https://stackoverflow.com/a/77243136 60 self.browser.execute_cdp_cmd( 61 "Page.addScriptToEvaluateOnNewDocument", 62 { 63 "source": """ 64 Element.prototype._attachShadow = Element.prototype.attachShadow; 65 Element.prototype.attachShadow = function () { 66 return this._attachShadow( { mode: "open" } ); 67 }; 68 """ 69 }, 70 ) 71 self.implicit_wait = implicit_wait 72 73 @property 74 def implicit_wait(self): 75 return self._implicit_wait 76 77 @implicit_wait.setter 78 def implicit_wait(self, value): 79 self._implicit_wait = value 80 if value is not None: 81 self.browser.implicitly_wait(value) 82 83 def get_shadow_root(self, shadow_root_css): 84 # get the shadow root of the element, this is necessary for 85 # elements that are hidden in the shadow dom 86 # see: https://stackoverflow.com/a/77243136 87 log.debug( 88 f"{DEBUG_HIGHLIGHT}get_shadow_root: {shadow_root_css}{DEBUG_HIGHLIGHT}" 89 ) 90 closed_shadow_host = self.find_by_css(shadow_root_css) 91 log.debug(f"closed_shadow_host: {closed_shadow_host}") 92 shadow_root = self.browser.execute_script( 93 "return arguments[0].shadowRoot", closed_shadow_host 94 ) 95 log.debug(f"shadow_root: {shadow_root}") 96 return shadow_root 97 98 def get(self, url): 99 self.browser.get(url) 100 101 def find_by_css(self, css=None, elem=None, as_list=False): 102 if css is None: 103 return None 104 if elem is None: 105 elem = self.browser 106 try: 107 if as_list: 108 return elem.find_elements(By.CSS_SELECTOR, css) 109 else: 110 return elem.find_element(By.CSS_SELECTOR, css) 111 except selenium_exceptions.WebDriverException as e: 112 log.info( 113 f"Unable to to find element: {css}", 114 ) 115 log.debug(e) 116 117 def find_by_text( 118 self, 119 text=None, 120 css=None, 121 elem=None, 122 as_list=False, 123 case_sensitive=False, 124 ): 125 if all([text, css]): 126 if elem is None: 127 elem = self.browser 128 result_as_list = [] 129 for item in self.find_by_css(css, elem=elem, as_list=True): 130 # convert to lower case if case insensitive is requested 131 item_text = item.text if case_sensitive else item.text.lower() 132 text = text if case_sensitive else text.lower() 133 if item_text == text: 134 if as_list: 135 result_as_list.append(item) 136 else: 137 return item 138 139 def wait_until(self, css=None, seconds=10, elem=None, want_bool=False): 140 if css is not None: 141 if elem is None: 142 elem = self.browser 143 try: 144 WebDriverWait(elem, seconds).until( 145 EC.presence_of_element_located((By.CSS_SELECTOR, css)) 146 ) 147 if want_bool: 148 return True 149 else: 150 return self.find_by_css(css=css, elem=elem) 151 except selenium_exceptions.TimeoutException: 152 log.info(f"Element not found: {css}") 153 return False 154 155 def save_screenshot(self, filename): 156 if self.path_of_screenshots: 157 self.path_of_screenshots = self.path_of_screenshots.rstrip("/") 158 filename = filename.lstrip("/") 159 filename = f"{self.path_of_screenshots}/{filename}" 160 161 if not pathlib.Path(filename).parent.exists(): 162 log.warning( 163 f"Destination for screenshots ({pathlib.Path(filename).parent}) does not exist." 164 ) 165 self.browser.save_screenshot(filename) 166 167 def zoom(self): 168 self.browser.execute_script("document.body.style.zoom='25%'") 169 170 def get_url(self): 171 current_url = self.browser.current_url 172 return current_url 173 174 def amount_of_elements(self, css=None, minimum=10): 175 if css is not None: 176 try: 177 count = self.browser.execute_script( 178 f'return document.querySelectorAll("{css}").length' 179 ) 180 if count >= minimum: 181 return True 182 else: 183 return False 184 except Exception as e: 185 log.error(f"Unable to find Element: {css}") 186 log.error(e) 187 return False 188 else: 189 log.error("CSS selector is None") 190 return False 191 192 def quit(self): 193 self.browser.quit()
A simple wrapper around the selenium webdriver to make it easier to use in the context of the availability checker.
SeleniumAgent( path_of_screenshots=None, header_arguments=[], implicit_wait=None, browser_dir=None, user_agent='\n Mozilla/5.0 (X11; Linux x86_64)\n AppleWebKit/537.36 (KHTML, like Gecko)\n Chrome/130.0.0.0 Safari/537.36\n ', window_size=(1920, 1080))
26 def __init__( 27 self, 28 path_of_screenshots=None, 29 header_arguments=[], 30 implicit_wait=None, 31 browser_dir=None, 32 user_agent=""" 33 Mozilla/5.0 (X11; Linux x86_64) 34 AppleWebKit/537.36 (KHTML, like Gecko) 35 Chrome/130.0.0.0 Safari/537.36 36 """, 37 window_size=(1920, 1080), 38 ): 39 # set up the chrome options 40 chrome_options = webdriver.ChromeOptions() 41 chrome_options.add_argument("--headless") 42 chrome_options.add_argument("--no-sandbox") 43 chrome_options.add_argument("--disable-dev-shm-usage") 44 if browser_dir is not None: 45 chrome_options.add_argument(f"--user-data-dir={browser_dir}") 46 if user_agent: 47 chrome_options.add_argument("user-agent=%s" % user_agent) 48 49 for argument in header_arguments: 50 log.info("Add additional header arguments: %s" % argument) 51 chrome_options.add_argument(argument) 52 53 self.path_of_screenshots = path_of_screenshots 54 self.browser = webdriver.Chrome(chrome_options) 55 self.browser.set_window_size(*window_size) 56 57 # we need to add a script to the browser to make it work with shadow 58 # dom which is used by React.js 59 # see: https://stackoverflow.com/a/77243136 60 self.browser.execute_cdp_cmd( 61 "Page.addScriptToEvaluateOnNewDocument", 62 { 63 "source": """ 64 Element.prototype._attachShadow = Element.prototype.attachShadow; 65 Element.prototype.attachShadow = function () { 66 return this._attachShadow( { mode: "open" } ); 67 }; 68 """ 69 }, 70 ) 71 self.implicit_wait = implicit_wait
def
get_shadow_root(self, shadow_root_css):
83 def get_shadow_root(self, shadow_root_css): 84 # get the shadow root of the element, this is necessary for 85 # elements that are hidden in the shadow dom 86 # see: https://stackoverflow.com/a/77243136 87 log.debug( 88 f"{DEBUG_HIGHLIGHT}get_shadow_root: {shadow_root_css}{DEBUG_HIGHLIGHT}" 89 ) 90 closed_shadow_host = self.find_by_css(shadow_root_css) 91 log.debug(f"closed_shadow_host: {closed_shadow_host}") 92 shadow_root = self.browser.execute_script( 93 "return arguments[0].shadowRoot", closed_shadow_host 94 ) 95 log.debug(f"shadow_root: {shadow_root}") 96 return shadow_root
def
find_by_css(self, css=None, elem=None, as_list=False):
101 def find_by_css(self, css=None, elem=None, as_list=False): 102 if css is None: 103 return None 104 if elem is None: 105 elem = self.browser 106 try: 107 if as_list: 108 return elem.find_elements(By.CSS_SELECTOR, css) 109 else: 110 return elem.find_element(By.CSS_SELECTOR, css) 111 except selenium_exceptions.WebDriverException as e: 112 log.info( 113 f"Unable to to find element: {css}", 114 ) 115 log.debug(e)
def
find_by_text( self, text=None, css=None, elem=None, as_list=False, case_sensitive=False):
117 def find_by_text( 118 self, 119 text=None, 120 css=None, 121 elem=None, 122 as_list=False, 123 case_sensitive=False, 124 ): 125 if all([text, css]): 126 if elem is None: 127 elem = self.browser 128 result_as_list = [] 129 for item in self.find_by_css(css, elem=elem, as_list=True): 130 # convert to lower case if case insensitive is requested 131 item_text = item.text if case_sensitive else item.text.lower() 132 text = text if case_sensitive else text.lower() 133 if item_text == text: 134 if as_list: 135 result_as_list.append(item) 136 else: 137 return item
def
wait_until(self, css=None, seconds=10, elem=None, want_bool=False):
139 def wait_until(self, css=None, seconds=10, elem=None, want_bool=False): 140 if css is not None: 141 if elem is None: 142 elem = self.browser 143 try: 144 WebDriverWait(elem, seconds).until( 145 EC.presence_of_element_located((By.CSS_SELECTOR, css)) 146 ) 147 if want_bool: 148 return True 149 else: 150 return self.find_by_css(css=css, elem=elem) 151 except selenium_exceptions.TimeoutException: 152 log.info(f"Element not found: {css}") 153 return False
def
save_screenshot(self, filename):
155 def save_screenshot(self, filename): 156 if self.path_of_screenshots: 157 self.path_of_screenshots = self.path_of_screenshots.rstrip("/") 158 filename = filename.lstrip("/") 159 filename = f"{self.path_of_screenshots}/{filename}" 160 161 if not pathlib.Path(filename).parent.exists(): 162 log.warning( 163 f"Destination for screenshots ({pathlib.Path(filename).parent}) does not exist." 164 ) 165 self.browser.save_screenshot(filename)
def
amount_of_elements(self, css=None, minimum=10):
174 def amount_of_elements(self, css=None, minimum=10): 175 if css is not None: 176 try: 177 count = self.browser.execute_script( 178 f'return document.querySelectorAll("{css}").length' 179 ) 180 if count >= minimum: 181 return True 182 else: 183 return False 184 except Exception as e: 185 log.error(f"Unable to find Element: {css}") 186 log.error(e) 187 return False 188 else: 189 log.error("CSS selector is None") 190 return False