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
path_of_screenshots
browser
implicit_wait
73    @property
74    def implicit_wait(self):
75        return self._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 get(self, url):
98    def get(self, url):
99        self.browser.get(url)
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 zoom(self):
167    def zoom(self):
168        self.browser.execute_script("document.body.style.zoom='25%'")
def get_url(self):
170    def get_url(self):
171        current_url = self.browser.current_url
172        return current_url
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
def quit(self):
192    def quit(self):
193        self.browser.quit()