示例#1
0
class Base(unittest.TestCase):
    """
    Base class for any technology to implement Selenium Interface Tests.

    This class instantiates the browser, reads the config file and prepares the log.

    If no config_path is passed, it will read the config.json file that exists in the same
    folder as the file that would execute this module.

    :param config_path: The path to the config file. - **Default:** "" (empty string)
    :type config_path: str
    :param autostart: Sets whether TIR should open browser and execute from the start. - **Default:** True
    :type: bool

    Usage:

    The Base class must be inherited by every internal class of each technology that would exist in this module.

    The classes must be declared under pwa/technologies/ folder.

    >>> def WebappInternal(Base):
    >>> def APWInternal(Base):
    """
    def __init__(self, config_path="", autostart=True):
        """
        Definition of each global variable:

        base_container: A variable to contain the layer element to be used on all methods.

        errors: A list that contains every error that should be sent to log at the end of the execution.

        language: Contains the terms defined in the language defined in config or found in the page.

        log: Object that controls the logs of the entire application.

        log.station: Property of the log that contains the machine's hostname.

        log_file: A variable to control when to generate a log file of each execution of web_scrap. (Debug purposes)

        wait: The global Selenium Wait defined to be used in the entire application.
        """
        #Global Variables:

        if config_path == "":
            config_path = os.path.join(sys.path[0], r"config.json")
        self.config = ConfigLoader(config_path)
        self.config.autostart = autostart

        self.language = LanguagePack(
            self.config.language) if self.config.language else ""
        self.log = Log(folder=self.config.log_folder)
        self.log.station = socket.gethostname()

        try:
            self.log.user = os.getlogin()
        except FileNotFoundError:
            import getpass
            self.log.user = getpass.getuser()

        self.base_container = "body"
        self.errors = []
        self.config.log_file = False

        if autostart:
            self.Start()

# Internal Methods

    def assert_result(self, expected):
        """
        [Internal]

        Asserts the result based on the expected value.

        :param expected: Expected value
        :type expected: bool

        Usage :

        >>> #Calling the method:
        >>> self.assert_result(True)
        """
        expected_assert = expected
        msg = "Passed"
        stack_item = next(
            iter(
                list(
                    map(
                        lambda x: x.function,
                        filter(lambda x: re.search('test_', x.function),
                               inspect.stack())))), None)
        test_number = f"{stack_item.split('_')[-1]} -" if stack_item else ""
        log_message = f"{test_number}"
        self.log.set_seconds()

        if self.errors:
            expected = not expected

            for field_msg in self.errors:
                log_message += (" " + field_msg)

            msg = log_message

            self.log.new_line(False, log_message)
        else:
            self.log.new_line(True, "")

        self.log.save_file()

        self.errors = []
        print(msg)
        if expected_assert:
            self.assertTrue(expected, msg)
        else:
            self.assertFalse(expected, msg)

    def click(self, element, click_type=enum.ClickType.JS, right_click=False):
        """
        [Internal]

        Clicks on the Selenium element.

        Supports three types of clicking: JavaScript, pure Selenium and Selenium's ActionChains.

        Default is JavaScript clicking.

        :param element: Selenium element
        :type element: Selenium object
        :param click_type: ClickType enum. - **Default:** enum.ClickType.JS
        :type click_type: enum.ClickType
        :param right_click: Clicks with the right button of the mouse in the last element of the tree.
        :type string: bool

        Usage:

        >>> #Defining the element:
        >>> element = lambda: self.driver.find_element_by_id("example_id")
        >>> #Calling the method
        >>> self.click(element(), click_type=enum.ClickType.JS)
        """
        try:
            if right_click:
                ActionChains(
                    self.driver).context_click(element).click().perform()
            else:
                self.scroll_to_element(element)
                if click_type == enum.ClickType.JS:
                    self.driver.execute_script("arguments[0].click()", element)
                elif click_type == enum.ClickType.SELENIUM:
                    element.click()
                elif click_type == enum.ClickType.ACTIONCHAINS:
                    ActionChains(self.driver).move_to_element(
                        element).click().perform()

            return True

        except StaleElementReferenceException:
            print("********Element Stale click*********")
            return False
        except Exception as e:
            print(f"Warning click method Exception: {str(e)}")
            return False

    def compare_field_values(self, field, user_value, captured_value, message):
        """
        [Internal]

        Validates and stores field in the self.errors array if the values are different.

        :param field: Field name
        :type field: str
        :param user_value: User input value
        :type user_value: str
        :param captured_value: Interface captured value
        :type captured_value: str
        :param message: Error message if comparison fails
        :type message: str

        Usage:

        >>> #Calling the method
        >>> self.compare_field_values("A1_NOME", "JOÃO", "JOOÃ", "Field A1_NOME has different values")
        """
        if str(user_value).strip() != str(captured_value).strip():
            self.errors.append(message)

    def double_click(self, element, click_type=enum.ClickType.SELENIUM):
        """
        [Internal]

        Clicks two times on the Selenium element.

        :param element: Selenium element
        :type element: Selenium object

        Usage:

        >>> #Defining the element:
        >>> element = lambda: self.driver.find_element_by_id("example_id")
        >>> #Calling the method
        >>> self.double_click(element())
        """
        try:
            if click_type == enum.ClickType.SELENIUM:
                self.scroll_to_element(element)
                element.click()
                element.click()
            elif click_type == enum.ClickType.ACTIONCHAINS:
                self.scroll_to_element(element)
                actions = ActionChains(self.driver)
                actions.move_to_element(element)
                actions.double_click()
                actions.perform()
            elif click_type == enum.ClickType.JS:
                self.driver.execute_script("arguments[0].click()", element)
                self.driver.execute_script("arguments[0].click()", element)

        except Exception:
            self.scroll_to_element(element)
            actions = ActionChains(self.driver)
            actions.move_to_element(element)
            actions.double_click()
            actions.perform()

    def element_exists(self,
                       term,
                       scrap_type=enum.ScrapType.TEXT,
                       position=0,
                       optional_term="",
                       main_container=".tmodaldialog,.ui-dialog"):
        """
        [Internal]

        Returns a boolean if element exists on the screen.

        :param term: The first term to use on a search of element
        :type term: str
        :param scrap_type: Type of element search. - **Default:** enum.ScrapType.TEXT
        :type scrap_type: enum.ScrapType
        :param position: Position which element is located. - **Default:** 0
        :type position: int
        :param optional_term: Second term to use on a search of element. Used in MIXED search. - **Default:** "" (empty string)
        :type optional_term: str

        :return: True if element is present. False if element is not present.
        :rtype: bool

        Usage:

        >>> element_is_present = element_exists(term=".ui-dialog", scrap_type=enum.ScrapType.CSS_SELECTOR)
        >>> element_is_present = element_exists(term=".tmodaldialog.twidget", scrap_type=enum.ScrapType.CSS_SELECTOR, position=initial_layer+1)
        >>> element_is_present = element_exists(term=text, scrap_type=enum.ScrapType.MIXED, optional_term=".tsay")
        """
        if self.config.debug_log:
            print(
                f"term={term}, scrap_type={scrap_type}, position={position}, optional_term={optional_term}"
            )

        if scrap_type == enum.ScrapType.SCRIPT:
            return bool(self.driver.execute_script(term))
        elif (scrap_type != enum.ScrapType.MIXED
              and scrap_type != enum.ScrapType.TEXT):
            selector = term
            if scrap_type == enum.ScrapType.CSS_SELECTOR:
                by = By.CSS_SELECTOR
            elif scrap_type == enum.ScrapType.XPATH:
                by = By.XPATH

            if scrap_type != enum.ScrapType.XPATH:
                soup = self.get_current_DOM()
                container_selector = self.base_container
                if (main_container is not None):
                    container_selector = main_container
                containers = self.zindex_sort(soup.select(container_selector),
                                              reverse=True)
                container = next(iter(containers), None)
                if not container:
                    return False

                try:
                    container_element = self.driver.find_element_by_xpath(
                        xpath_soup(container))
                except:
                    return False
            else:
                container_element = self.driver

            element_list = container_element.find_elements(by, selector)
        else:
            if scrap_type == enum.ScrapType.MIXED:
                selector = optional_term
            else:
                selector = "div"

            element_list = self.web_scrap(term=term,
                                          scrap_type=scrap_type,
                                          optional_term=optional_term,
                                          main_container=main_container)
        if position == 0:
            return len(element_list) > 0
        else:
            return len(element_list) >= position

    def filter_displayed_elements(self, elements, reverse=False):
        """
        [Internal]

        Receives a BeautifulSoup element list and filters only the displayed elements.

        :param elements: BeautifulSoup element list
        :type elements: List of BeautifulSoup objects
        :param reverse: Boolean value if order should be reversed or not. - **Default:** False
        :type reverse: bool

        :return: List of filtered BeautifulSoup elements
        :rtype: List of BeautifulSoup objects

        Usage:

        >>> #Defining the element list:
        >>> soup = self.get_current_DOM()
        >>> elements = soup.select("div")
        >>> #Calling the method
        >>> self.filter_displayed_elements(elements, True)
        """
        #0 - elements filtered
        elements = list(
            filter(lambda x: self.soup_to_selenium(x) is not None, elements))
        if not elements:
            return
        #1 - Create an enumerated list from the original elements
        indexed_elements = list(enumerate(elements))
        #2 - Convert every element from the original list to selenium objects
        selenium_elements = list(
            map(lambda x: self.soup_to_selenium(x), elements))
        #3 - Create an enumerated list from the selenium objects
        indexed_selenium_elements = list(enumerate(selenium_elements))
        #4 - Filter elements based on "is_displayed()" and gets the filtered elements' enumeration
        filtered_selenium_indexes = list(
            map(
                lambda x: x[0],
                filter(lambda x: x[1].is_displayed(),
                       indexed_selenium_elements)))
        #5 - Use List Comprehension to build a filtered list from the elements based on enumeration
        filtered_elements = [
            x[1] for x in indexed_elements if x[0] in filtered_selenium_indexes
        ]
        #6 - Sort the result and return it
        return self.zindex_sort(filtered_elements, reverse)

    def find_first_div_parent(self, element):
        """
        [Internal]

        Finds first div parent element of another BeautifulSoup element.

        If element is already a div, it will return the element.

        :param element: BeautifulSoup element
        :type element: BeautifulSoup object

        :return: The first div parent of the element
        :rtype: BeautifulSoup object

        Usage:

        >>> parent_element = self.find_first_div_parent(my_element)
        """
        current = element
        while (hasattr(current, "name")
               and self.element_name(current) != "div"):
            current = current.find_parent()
        return current

    def element_name(self, element_soup):
        """
        [internal]

        """
        result = ''
        if element_soup:
            result = element_soup.name
        return result

    def find_label_element(self, label_text, container):
        """
        [Internal]

        Find input element next to label containing the label_text parameter.

        :param label_text: The label text to be searched
        :type label_text: str
        :param container: The main container object to be used
        :type container: BeautifulSoup object

        :return: A list containing a BeautifulSoup object next to the label
        :rtype: List of BeautifulSoup objects

        Usage:

        >>> self.find_label_element("User:"******"""
        element = next(
            iter(
                list(
                    map(
                        lambda x: self.find_first_div_parent(x),
                        container.find_all(
                            text=re.compile(f"^{re.escape(label_text)}" +
                                            r"(\*?)(\s*?)$"))))), None)
        if element is None:
            return []

        next_sibling = element.find_next_sibling("input")
        if next_sibling:
            return [next_sibling]
        else:
            return []

    def get_current_DOM(self):
        """
        [Internal]

        Returns current HTML DOM parsed as a BeautifulSoup object

        :returns: BeautifulSoup parsed DOM
        :rtype: BeautifulSoup object

        Usage:

        >>> #Calling the method
        >>> soup = self.get_current_DOM()
        """
        try:

            soup = BeautifulSoup(self.driver.page_source, "html.parser")

            if soup and soup.select('.session'):

                script = """
                var getIframe = () => {
                    if(document.querySelector(".session")){
                        var iframeObject = document.querySelector(".session")
                        var contet = iframeObject.contentDocument;
                        var serializer = new XMLSerializer();
                        return serializer.serializeToString(contet);
                    }
                    return ""
                }

                return getIframe()
                """
                soup = BeautifulSoup(self.driver.execute_script(script),
                                     'html.parser')
                self.driver.switch_to.frame(
                    self.driver.find_element_by_css_selector(
                        "iframe[class=session]"))

            return soup

        except WebDriverException as e:
            self.driver.switch_to.default_content()
            soup = BeautifulSoup(self.driver.page_source, "html.parser")
            return soup

    def get_element_text(self, element):
        """
        [Internal]

        Gets element text.

        :param element: Selenium element
        :type element: Selenium object

        :return: Element text
        :rtype: str

        Usage:

        >>> #Defining the element:
        >>> element = lambda: self.driver.find_element_by_id("example_id")
        >>> #Calling the method
        >>> text = self.get_element_text(element())
        """
        try:
            return self.driver.execute_script("return arguments[0].innerText",
                                              element)
        except StaleElementReferenceException:
            print("********Element Stale get_element_text*********")
            pass

    def get_element_value(self, element):
        """
        [Internal]

        Gets element value.

        :param element: Selenium element
        :type element: Selenium object

        :return: Element value
        :rtype: str

        Usage:

        >>> #Defining the element:
        >>> element = lambda: self.driver.find_element_by_id("example_id")
        >>> #Calling the method
        >>> text = self.get_element_value(element())
        """
        try:
            return self.driver.execute_script("return arguments[0].value",
                                              element)
        except StaleElementReferenceException:
            print("********Element Stale get_element_value*********")
            pass

    def log_error(self, message, new_log_line=True):
        """
        [Internal]

        Finishes execution of test case with an error and creates the log information for that test.

        :param message: Message to be logged
        :type message: str
        :param new_log_line: Boolean value if Message should be logged as new line or not. - **Default:** True
        :type new_log_line: bool

        Usage:

        >>> #Calling the method:
        >>> self.log_error("Element was not found")
        """
        stack_item = next(
            iter(
                list(
                    map(
                        lambda x: x.function,
                        filter(lambda x: re.search('test_', x.function),
                               inspect.stack())))), None)
        test_number = f"{stack_item.split('_')[-1]} -" if stack_item else ""
        log_message = f"{test_number} {message}"
        self.log.set_seconds()

        if new_log_line:
            self.log.new_line(False, log_message)
        self.log.save_file()
        self.assertTrue(False, log_message)

    def move_to_element(self, element):
        """
        [Internal]

        Move focus to element on the screen.

        :param element: Selenium element
        :type element: Selenium object

        Usage:

        >>> #Defining an element:
        >>> element = lambda: self.driver.find_element_by_id("example_id")
        >>> #Calling the method
        >>> self.scroll_to_element(element())
        """
        ActionChains(self.driver).move_to_element(element).perform()

    def normalize_config_name(self, config_name):
        """
        [Internal]

        Normalizes the config name string to respect the config object
        naming convention.

        :param config_name: The config name string to be normalized.
        :type config_name: str
        :return: The config name string normalized.
        :rtype: str

        Usage:

        >>> # Calling the method:
        >>> normalized_name = self.normalize_config_name("InitialProgram") # "initial_program"
        """
        name_letters = list(map(lambda x: x, config_name))
        capitalized = list(
            filter(lambda x: x[1] in string.ascii_uppercase,
                   enumerate(name_letters)))
        normalized = ""
        if len(capitalized) > 1:
            words = []
            for count in range(0, len(capitalized)):
                if count + 1 < len(capitalized):
                    word = "".join(
                        name_letters[capitalized[count][0]:capitalized[count +
                                                                       1][0]])
                else:
                    word = "".join(name_letters[capitalized[count][0]:])
                words.append(word.lower())
            normalized = "_".join(words)
        else:
            normalized = config_name.lower()

        return normalized

    def take_screenshot(self, filename):
        """
        [Internal]

        Takes a screenshot and saves on the screenshot folder defined in config.

        :param filename: The name of the screenshot file.
        :type: str

        Usage:

        >>> # Calling the method:
        >>> self.take_screenshot(filename="myscreenshot")
        """
        if not filename.endswith(".png"):
            filename += ".png"

        directory = self.config.screenshot_folder if self.config.screenshot_folder else os.path.join(
            os.getcwd(), "screenshot")

        if not os.path.exists(directory):
            os.makedirs(directory)

        fullpath = os.path.join(directory, filename)

        self.driver.save_screenshot(fullpath)

    def scroll_to_element(self, element):
        """
        [Internal]

        Scroll to element on the screen.

        :param element: Selenium element
        :type element: Selenium object

        Usage:

        >>> #Defining an element:
        >>> element = lambda: self.driver.find_element_by_id("example_id")
        >>> #Calling the method
        >>> self.scroll_to_element(element())
        """
        try:
            self.driver.execute_script("return arguments[0].scrollIntoView();",
                                       element)
        except StaleElementReferenceException:
            print("********Element Stale scroll_to_element*********")
            pass

    def search_zindex(self, element):
        """
        [Internal]

        Returns zindex value of BeautifulSoup object.

        Internal function created to be used inside lambda of zindex_sort method.

        Only works if element has Style attribute.

        :param element: BeautifulSoup element
        :type element: BeautifulSoup object

        :return: z-index value
        :rtype: int

        Usage:

        >>> #Line extracted from zindex_sort method:
        >>> elements.sort(key=lambda x: self.search_zindex(x), reverse=reverse)

        """
        zindex = 0
        if hasattr(
                element, "attrs"
        ) and "style" in element.attrs and "z-index:" in element.attrs['style']:
            zindex = int(element.attrs['style'].split("z-index:")[1].split(";")
                         [0].strip())

        return zindex

    def select_combo(self, element, option):
        """
        Selects the option on the combobox.

        :param element: Combobox element
        :type element: Beautiful Soup object
        :param option: Option to be selected
        :type option: str

        Usage:

        >>> #Calling the method:
        >>> self.select_combo(element, "Chosen option")
        """
        combo = Select(self.driver.find_element_by_xpath(xpath_soup(element)))
        value = next(
            iter(
                filter(lambda x: x.text[0:len(option)] == option,
                       combo.options)), None)

        if value:
            time.sleep(1)
            text_value = value.text
            combo.select_by_visible_text(text_value)
            print(f"Selected value for combo is: {text_value}")

    def send_keys(self, element, arg):
        """
        [Internal]

        Clicks two times on the Selenium element.

        :param element: Selenium element
        :type element: Selenium object
        :param arg: Text or Keys to be sent to the element
        :type arg: str or selenium.webdriver.common.keys

        Usage:

        >>> #Defining the element:
        >>> element = lambda: self.driver.find_element_by_id("example_id")
        >>> #Calling the method with a string
        >>> self.send_keys(element(), "Text")
        >>> #Calling the method with a Key
        >>> self.send_keys(element(), Keys.ENTER)
        """
        try:
            if arg.isprintable():
                element.clear()
                element.send_keys(Keys.CONTROL, 'a')
            element.send_keys(arg)
        except Exception:
            actions = ActionChains(self.driver)
            actions.move_to_element(element)
            actions.click()
            if arg.isprintable():
                actions.key_down(Keys.CONTROL).send_keys('a').key_up(
                    Keys.CONTROL).send_keys(Keys.DELETE)
            actions.send_keys(Keys.HOME)
            actions.send_keys(arg)
            actions.perform()

    def search_stack(self, function):
        """
        Returns True if passed function is present in the call stack.

        :param function: Name of the function
        :type function: str

        :return: Boolean if passed function is present or not in the call stack.
        :rtype: bool

        Usage:

        >>> # Calling the method:
        >>> is_present = self.search_stack("MATA020")
        """
        return len(
            list(filter(lambda x: x.function == function,
                        inspect.stack()))) > 0

    def set_element_focus(self, element):
        """
        [Internal]

        Sets focus on element.

        :param element: Selenium element
        :type element: Selenium object

        Usage:

        >>> #Defining the element:
        >>> element = lambda: self.driver.find_element_by_id("example_id")
        >>> #Calling the method
        >>> text = self.set_element_focus(element())
        """
        try:
            self.driver.execute_script("window.focus(); arguments[0].focus();",
                                       element)
        except StaleElementReferenceException:
            print("********Element Stale set_element_focus*********")
            pass

    def soup_to_selenium(self, soup_object):
        """
        [Internal]

        An abstraction of the Selenium call to simplify the conversion of elements.

        :param soup_object: The BeautifulSoup object to be converted.
        :type soup_object: BeautifulSoup object

        :return: The object converted to a Selenium object.
        :rtype: Selenium object

        Usage:

        >>> # Calling the method:
        >>> selenium_obj = lambda: self.soup_to_selenium(bs_obj)
        """
        if soup_object is None:
            raise AttributeError
        return next(
            iter(self.driver.find_elements_by_xpath(xpath_soup(soup_object))),
            None)

    def web_scrap(self,
                  term,
                  scrap_type=enum.ScrapType.TEXT,
                  optional_term=None,
                  label=False,
                  main_container=None):
        """
        [Internal]

        Returns a BeautifulSoup object list based on the search parameters.

        Does not support ScrapType.XPATH as scrap_type parameter value.

        :param term: The first search term. A text or a selector
        :type term: str
        :param scrap_type: The type of webscraping. - **Default:** enum.ScrapType.TEXT
        :type scrap_type: enum.ScrapType.
        :param optional_term: The second search term. A selector used in MIXED webscraping. - **Default:** None
        :type optional_term: str
        :param label: If the search is based on a label near the element. - **Default:** False
        :type label: bool
        :param main_container: The selector of a container element that has all other elements. - **Default:** None
        :type main_container: str

        :return: List of BeautifulSoup4 elements based on search parameters.
        :rtype: List of BeautifulSoup4 objects

        Usage:

        >>> #All buttons
        >>> buttons = self.web_scrap(term="button", scrap_type=enum.ScrapType.CSS_SELECTOR)
        >>> #----------------#
        >>> #Elements that contain the text "Example"
        >>> example_elements = self.web_scrap(term="Example")
        >>> #----------------#
        >>> #Elements with class "my_class" and text "my_text"
        >>> elements = self.web_scrap(term="my_text", scrap_type=ScrapType.MIXED, optional_term=".my_class")
        """
        try:
            endtime = time.time() + 60
            container = None
            while (time.time() < endtime and container is None):
                soup = self.get_current_DOM()

                if self.config.log_file:
                    with open(
                            f"{term + str(scrap_type) + str(optional_term) + str(label) + str(main_container) + str(random.randint(1, 101)) }.txt",
                            "w") as text_file:
                        text_file.write(f" HTML CONTENT: {str(soup)}")

                container_selector = self.base_container
                if (main_container is not None):
                    container_selector = main_container

                containers = self.zindex_sort(soup.select(container_selector),
                                              reverse=True)

                container = next(iter(containers), None)

            if container is None:
                raise Exception("Couldn't find container")

            if (scrap_type == enum.ScrapType.TEXT):
                if label:
                    return self.find_label_element(term, container)
                else:
                    return list(
                        filter(lambda x: term.lower() in x.text.lower(),
                               container.select("div > *")))
            elif (scrap_type == enum.ScrapType.CSS_SELECTOR):
                return container.select(term)
            elif (scrap_type == enum.ScrapType.MIXED
                  and optional_term is not None):
                return list(
                    filter(lambda x: term.lower() in x.text.lower(),
                           container.select(optional_term)))
            elif (scrap_type == enum.ScrapType.SCRIPT):
                script_result = self.driver.execute_script(term)
                return script_result if isinstance(script_result, list) else []
            else:
                return []
        except Exception as e:
            self.log_error(str(e))

    def zindex_sort(self, elements, reverse=False):
        """
        [Internal]

        Sorts list of BeautifulSoup elements based on z-index style attribute.

        Only works if elements have Style attribute.

        :param elements: BeautifulSoup element list
        :type elements: List of BeautifulSoup objects
        :param reverse: Boolean value if order should be reversed or not. - **Default:** False
        :type reverse: bool

        :return: List of sorted BeautifulSoup elements based on zindex.
        :rtype: List of BeautifulSoup objects

        Usage:

        >>> #Defining the element list:
        >>> soup = self.get_current_DOM()
        >>> elements = soup.select("div")
        >>> #Calling the method
        >>> self.zindex_sort(elements, True)
        """
        elements.sort(key=lambda x: self.search_zindex(x), reverse=reverse)
        return elements

# User Methods

    def AssertFalse(self):
        """
        Defines that the test case expects a False response to pass

        Usage:

        >>> #Calling the method
        >>> oHelper.AssertFalse()
        """
        self.assert_result(False)

    def AssertTrue(self):
        """
        Defines that the test case expects a True response to pass

        Usage:

        >>> #Calling the method
        >>> oHelper.AssertTrue()
        """
        self.assert_result(True)

    def SetTIRConfig(self, config_name, value):
        """
        Changes a value of a TIR internal config during runtime.

        This could be useful for TestCases that must use a different set of configs
        than the ones defined at **config.json**

        Available configs:

        - Url - str
        - Environment - str
        - User - str
        - Password - str
        - Language - str
        - DebugLog - str
        - TimeOut - int
        - InitialProgram - str
        - Routine - str
        - Date - str
        - Group - str
        - Branch - str
        - Module - str

        :param config_name: The config to be changed.
        :type config_name: str
        :param value: The value that would be set.
        :type value: str

        Usage:

        >>> # Calling the method:
        >>> oHelper.SetTIRConfig(config_name="date", value="30/10/2018")
        """
        if 'TimeOut' in config_name:
            print('TimeOut setting has been disabled in SetTirConfig')
        else:
            print(f"Setting config: {config_name} = {value}")
            normalized_config = self.normalize_config_name(config_name)
            setattr(self.config, normalized_config, value)

    def Start(self):
        """
        Opens the browser maximized and goes to defined URL.

        Usage:

        >>> # Calling the method:
        >>> oHelper.Start()
        """
        print("Starting the browser")
        if self.config.browser.lower() == "firefox":
            if sys.platform == 'linux':
                driver_path = os.path.join(os.path.dirname(__file__),
                                           r'drivers/linux64/geckodriver')
            else:
                driver_path = os.path.join(
                    os.path.dirname(__file__),
                    r'drivers\\windows\\geckodriver.exe')
            log_path = os.devnull

            options = FirefoxOpt()
            options.set_headless(self.config.headless)
            self.driver = webdriver.Firefox(firefox_options=options,
                                            executable_path=driver_path,
                                            log_path=log_path)
        elif self.config.browser.lower() == "chrome":
            driver_path = os.path.join(os.path.dirname(__file__),
                                       r'drivers\\windows\\chromedriver.exe')
            options = ChromeOpt()
            options.set_headless(self.config.headless)
            options.add_argument('--log-level=3')
            if self.config.headless:
                options.add_argument('force-device-scale-factor=0.77')

            self.driver = webdriver.Chrome(chrome_options=options,
                                           executable_path=driver_path)
        elif self.config.browser.lower() == "electron":
            driver_path = os.path.join(
                os.path.dirname(__file__),
                r'drivers\\windows\\electron\\chromedriver.exe'
            )  # TODO chromedriver electron version
            options = ChromeOpt()
            options.add_argument('--log-level=3')
            options.add_argument(f'--environment="{self.config.environment}"')
            options.add_argument(f'--url="{self.config.url}"')
            options.add_argument(f'--program="{self.config.start_program}"')
            options.add_argument('--quiet')
            options.binary_location = self.config.electron_binary_path
            self.driver = webdriver.Chrome(options=options,
                                           executable_path=driver_path)

        if not self.config.browser.lower() == "electron":
            if self.config.headless:
                self.driver.set_window_position(0, 0)
                self.driver.set_window_size(1366, 768)
            else:
                self.driver.maximize_window()

            self.driver.get(self.config.url)

        self.wait = WebDriverWait(self.driver, 90)

    def TearDown(self):
        """
        Closes the webdriver and ends the test case.

        Usage:

        >>> #Calling the method
        >>> oHelper.TearDown()
        """
        self.driver.close()
示例#2
0
class Base(unittest.TestCase):
    """
    Base class for any technology to implement Selenium Interface Tests.

    This class instantiates the browser, reads the config file and prepares the log.

    If no config_path is passed, it will read the config.json file that exists in the same
    folder as the file that would execute this module.

    :param config_path: The path to the config file. - **Default:** "" (empty string)
    :type config_path: str

    Usage:

    The Base class must be inherited by every internal class of each technology that would exist in this module.

    The classes must be declared under pwa/technologies/ folder.

    >>> def WebappInternal(Base):
    >>> def APWInternal(Base):
    """
    def __init__(self, config_path=""):
        """
        Definition of each global variable:

        base_container: A variable to contain the layer element to be used on all methods.

        errors: A list that contains every error that should be sent to log at the end of the execution.

        language: Contains the terms defined in the language defined in config or found in the page.

        log: Object that controls the logs of the entire application.

        log.station: Property of the log that contains the machine's hostname.

        log_file: A variable to control when to generate a log file of each execution of web_scrap. (Debug purposes)

        wait: The global Selenium Wait defined to be used in the entire application.
        """
        if config_path == "":
            config_path = os.path.join(sys.path[0], r"config.json")
        self.config = ConfigLoader(config_path)

        if self.config.browser.lower() == "firefox":
            driver_path = os.path.join(os.path.dirname(__file__),
                                       r'drivers\\geckodriver.exe')
            log_path = os.path.join(os.path.dirname(__file__),
                                    r'geckodriver.log')
            options = FirefoxOpt()
            options.set_headless(self.config.headless)
            self.driver = webdriver.Firefox(firefox_options=options,
                                            executable_path=driver_path,
                                            log_path=log_path)
        elif self.config.browser.lower() == "chrome":
            driver_path = os.path.join(os.path.dirname(__file__),
                                       r'drivers\\chromedriver.exe')
            options = ChromeOpt()
            options.set_headless(self.config.headless)
            self.driver = webdriver.Chrome(chrome_options=options,
                                           executable_path=driver_path)

        self.driver.maximize_window()
        self.driver.get(self.config.url)

        #Global Variables:

        self.wait = WebDriverWait(self.driver, 5)

        self.language = LanguagePack(
            self.config.language) if self.config.language else ""
        self.log = Log(folder=self.config.log_folder)
        self.log.station = socket.gethostname()

        self.base_container = "body"
        self.errors = []
        self.config.log_file = False

# Internal Methods

    def assert_result(self, expected):
        """
        [Internal]

        Asserts the result based on the expected value.

        :param expected: Expected value
        :type expected: bool

        Usage :

        >>> #Calling the method:
        >>> self.assert_result(True)
        """
        expected_assert = expected
        msg = "Passed"
        stack_item = next(
            iter(
                list(
                    map(
                        lambda x: x.function,
                        filter(lambda x: re.search('test_', x.function),
                               inspect.stack())))), None)
        test_number = f"{stack_item.split('_')[-1]} -" if stack_item else ""
        log_message = f"{test_number}"
        self.log.set_seconds()

        if self.errors:
            expected = not expected

            for field_msg in self.errors:
                log_message += (" " + field_msg)

            msg = log_message

            self.log.new_line(False, log_message)
        else:
            self.log.new_line(True, "")

        self.log.save_file()

        self.errors = []
        print(msg)
        if expected_assert:
            self.assertTrue(expected, msg)
        else:
            self.assertFalse(expected, msg)

    def click(self, element, click_type=enum.ClickType.JS):
        """
        [Internal]

        Clicks on the Selenium element.

        Supports three types of clicking: JavaScript, pure Selenium and Selenium's ActionChains.

        Default is JavaScript clicking.

        :param element: Selenium element
        :type element: Selenium object
        :param click_type: ClickType enum. - **Default:** enum.ClickType.JS
        :type click_type: enum.ClickType

        Usage:

        >>> #Defining the element:
        >>> element = lambda: self.driver.find_element_by_id("example_id")
        >>> #Calling the method
        >>> self.click(element(), type=enum.ClickType.JS)
        """
        try:
            self.scroll_to_element(element)
            if click_type == enum.ClickType.JS:
                self.driver.execute_script("arguments[0].click()", element)
            elif click_type == enum.ClickType.SELENIUM:
                element.click()
            elif click_type == enum.ClickType.ACTIONCHAINS:
                ActionChains(
                    self.driver).move_to_element(element).click().perform()
        except Exception as error:
            self.log_error(str(error))

    def compare_field_values(self, field, user_value, captured_value, message):
        """
        [Internal]

        Validates and stores field in the self.errors array if the values are different.

        :param field: Field name
        :type field: str
        :param user_value: User input value
        :type user_value: str
        :param captured_value: Interface captured value
        :type captured_value: str
        :param message: Error message if comparison fails
        :type message: str

        Usage:

        >>> #Calling the method
        >>> self.compare_field_values("A1_NOME", "JOÃO", "JOOÃ", "Field A1_NOME has different values")
        """
        if str(user_value).strip() != str(captured_value).strip():
            self.errors.append(message)

    def double_click(self, element):
        """
        [Internal]

        Clicks two times on the Selenium element.

        :param element: Selenium element
        :type element: Selenium object

        Usage:

        >>> #Defining the element:
        >>> element = lambda: self.driver.find_element_by_id("example_id")
        >>> #Calling the method
        >>> self.double_click(element())
        """
        try:
            self.scroll_to_element(element)
            element.click()
            element.click()
        except Exception:
            self.scroll_to_element(element)
            actions = ActionChains(self.driver)
            actions.move_to_element(element)
            actions.double_click()
            actions.perform()

    def element_exists(self,
                       term,
                       scrap_type=enum.ScrapType.TEXT,
                       position=0,
                       optional_term="",
                       main_container=".tmodaldialog,.ui-dialog"):
        """
        [Internal]

        Returns a boolean if element exists on the screen.

        :param term: The first term to use on a search of element
        :type term: str
        :param scrap_type: Type of element search. - **Default:** enum.ScrapType.TEXT
        :type scrap_type: enum.ScrapType
        :param position: Position which element is located. - **Default:** 0
        :type position: int
        :param optional_term: Second term to use on a search of element. Used in MIXED search. - **Default:** "" (empty string)
        :type optional_term: str

        :return: True if element is present. False if element is not present.
        :rtype: bool

        Usage:

        >>> element_is_present = element_exists(term=".ui-dialog", scrap_type=enum.ScrapType.CSS_SELECTOR)
        >>> element_is_present = element_exists(term=".tmodaldialog.twidget", scrap_type=enum.ScrapType.CSS_SELECTOR, position=initial_layer+1)
        >>> element_is_present = element_exists(term=text, scrap_type=enum.ScrapType.MIXED, optional_term=".tsay")
        """
        if self.config.debug_log:
            print(
                f"term={term}, scrap_type={scrap_type}, position={position}, optional_term={optional_term}"
            )

        if scrap_type == enum.ScrapType.SCRIPT:
            return bool(self.driver.execute_script(term))
        elif (scrap_type != enum.ScrapType.MIXED
              and scrap_type != enum.ScrapType.TEXT):
            selector = term
            if scrap_type == enum.ScrapType.CSS_SELECTOR:
                by = By.CSS_SELECTOR
            elif scrap_type == enum.ScrapType.XPATH:
                by = By.XPATH

            if scrap_type != enum.ScrapType.XPATH:
                soup = self.get_current_DOM()
                container_selector = self.base_container
                if (main_container is not None):
                    container_selector = main_container
                containers = self.zindex_sort(soup.select(container_selector),
                                              reverse=True)
                container = next(iter(containers), None)
                if not container:
                    return False

                try:
                    container_element = self.driver.find_element_by_xpath(
                        xpath_soup(container))
                except:
                    return False
            else:
                container_element = self.driver

            element_list = container_element.find_elements(by, selector)
        else:
            if scrap_type == enum.ScrapType.MIXED:
                selector = optional_term
            else:
                selector = "div"

            element_list = self.web_scrap(term=term,
                                          scrap_type=scrap_type,
                                          optional_term=optional_term,
                                          main_container=main_container)
        if position == 0:
            return len(element_list) > 0
        else:
            return len(element_list) >= position

    def filter_displayed_elements(self, elements, reverse=False):
        """
        [Internal]

        Receives a BeautifulSoup element list and filters only the displayed elements.

        :param elements: BeautifulSoup element list
        :type elements: List of BeautifulSoup objects
        :param reverse: Boolean value if order should be reversed or not. - **Default:** False
        :type reverse: bool

        :return: List of filtered BeautifulSoup elements
        :rtype: List of BeautifulSoup objects

        Usage:

        >>> #Defining the element list:
        >>> soup = self.get_current_DOM()
        >>> elements = soup.select("div")
        >>> #Calling the method
        >>> self.filter_displayed_elements(elements, True)
        """
        #1 - Create an enumerated list from the original elements
        indexed_elements = list(enumerate(elements))
        #2 - Convert every element from the original list to selenium objects
        selenium_elements = list(
            map(lambda x: self.driver.find_element_by_xpath(xpath_soup(x)),
                elements))
        #3 - Create an enumerated list from the selenium objects
        indexed_selenium_elements = list(enumerate(selenium_elements))
        #4 - Filter elements based on "is_displayed()" and gets the filtered elements' enumeration
        filtered_selenium_indexes = list(
            map(
                lambda x: x[0],
                filter(lambda x: x[1].is_displayed(),
                       indexed_selenium_elements)))
        #5 - Use List Comprehension to build a filtered list from the elements based on enumeration
        filtered_elements = [
            x[1] for x in indexed_elements if x[0] in filtered_selenium_indexes
        ]
        #6 - Sort the result and return it
        return self.zindex_sort(filtered_elements, reverse)

    def find_first_div_parent(self, element):
        """
        [Internal]

        Finds first div parent element of another BeautifulSoup element.

        If element is already a div, it will return the element.

        :param element: BeautifulSoup element
        :type element: BeautifulSoup object

        :return: The first div parent of the element
        :rtype: BeautifulSoup object

        Usage:

        >>> parent_element = self.find_first_div_parent(my_element)
        """
        current = element
        while (hasattr(current, "name") and current.name != "div"):
            current = current.find_parent()
        return current

    def find_label_element(self, label_text, container):
        """
        [Internal]

        Find input element next to label containing the label_text parameter.

        :param label_text: The label text to be searched
        :type label_text: str
        :param container: The main container object to be used
        :type container: BeautifulSoup object

        :return: A list containing a BeautifulSoup object next to the label
        :rtype: List of BeautifulSoup objects

        Usage:

        >>> self.find_label_element("User:"******"""
        element = next(
            iter(
                list(
                    map(
                        lambda x: self.find_first_div_parent(x),
                        container.find_all(
                            text=re.compile(f"^{re.escape(label_text)}" +
                                            r"(\*?)(\s*?)$"))))), None)
        if element is None:
            return []

        next_sibling = element.find_next_sibling("input")
        if next_sibling:
            return [next_sibling]
        else:
            return []

    def get_current_DOM(self):
        """
        [Internal]

        Returns current HTML DOM parsed as a BeautifulSoup object

        :returns: BeautifulSoup parsed DOM
        :rtype: BeautifulSoup object

        Usage:

        >>> #Calling the method
        >>> soup = self.get_current_DOM()
        """
        return BeautifulSoup(self.driver.page_source, "html.parser")

    def get_element_text(self, element):
        """
        [Internal]

        Gets element text.

        :param element: Selenium element
        :type element: Selenium object

        :return: Element text
        :rtype: str

        Usage:

        >>> #Defining the element:
        >>> element = lambda: self.driver.find_element_by_id("example_id")
        >>> #Calling the method
        >>> text = self.get_element_text(element())
        """
        return self.driver.execute_script("return arguments[0].innerText",
                                          element)

    def get_element_value(self, element):
        """
        [Internal]

        Gets element value.

        :param element: Selenium element
        :type element: Selenium object

        :return: Element value
        :rtype: str

        Usage:

        >>> #Defining the element:
        >>> element = lambda: self.driver.find_element_by_id("example_id")
        >>> #Calling the method
        >>> text = self.get_element_value(element())
        """
        return self.driver.execute_script("return arguments[0].value", element)

    def log_error(self, message, new_log_line=True):
        """
        [Internal]

        Finishes execution of test case with an error and creates the log information for that test.

        :param message: Message to be logged
        :type message: str
        :param new_log_line: Boolean value if Message should be logged as new line or not. - **Default:** True
        :type new_log_line: bool

        Usage:

        >>> #Calling the method:
        >>> self.log_error("Element was not found")
        """
        stack_item = next(
            iter(
                list(
                    map(
                        lambda x: x.function,
                        filter(lambda x: re.search('test_', x.function),
                               inspect.stack())))), None)
        test_number = f"{stack_item.split('_')[-1]} -" if stack_item else ""
        log_message = f"{test_number} {message}"
        self.log.set_seconds()

        if new_log_line:
            self.log.new_line(False, log_message)
        self.log.save_file()
        self.assertTrue(False, log_message)

    def move_to_element(self, element):
        """
        [Internal]

        Move focus to element on the screen.

        :param element: Selenium element
        :type element: Selenium object

        Usage:

        >>> #Defining an element:
        >>> element = lambda: self.driver.find_element_by_id("example_id")
        >>> #Calling the method
        >>> self.scroll_to_element(element())
        """
        ActionChains(self.driver).move_to_element(element).perform()

    def scroll_to_element(self, element):
        """
        [Internal]

        Scroll to element on the screen.

        :param element: Selenium element
        :type element: Selenium object

        Usage:

        >>> #Defining an element:
        >>> element = lambda: self.driver.find_element_by_id("example_id")
        >>> #Calling the method
        >>> self.scroll_to_element(element())
        """
        if element.get_attribute("id"):
            self.driver.execute_script(
                "return document.getElementById('{}').scrollIntoView();".
                format(element.get_attribute("id")))
        else:
            self.driver.execute_script("return arguments[0].scrollIntoView();",
                                       element)

    def search_zindex(self, element):
        """
        [Internal]

        Returns zindex value of BeautifulSoup object.

        Internal function created to be used inside lambda of zindex_sort method.

        Only works if element has Style attribute.

        :param element: BeautifulSoup element
        :type element: BeautifulSoup object

        :return: z-index value
        :rtype: int

        Usage:

        >>> #Line extracted from zindex_sort method:
        >>> elements.sort(key=lambda x: self.search_zindex(x), reverse=reverse)

        """
        zindex = 0
        if hasattr(
                element, "attrs"
        ) and "style" in element.attrs and "z-index:" in element.attrs['style']:
            zindex = int(element.attrs['style'].split("z-index:")[1].split(";")
                         [0].strip())

        return zindex

    def select_combo(self, element, option):
        """
        Selects the option on the combobox.

        :param element: Combobox element
        :type element: Beautiful Soup object
        :param option: Option to be selected
        :type option: str

        Usage:

        >>> #Calling the method:
        >>> self.select_combo(element, "Chosen option")
        """
        combo = Select(self.driver.find_element_by_xpath(xpath_soup(element)))
        value = next(
            iter(
                filter(lambda x: x.text[0:len(option)] == option,
                       combo.options)), None)

        if value:
            time.sleep(1)
            text_value = value.text
            combo.select_by_visible_text(text_value)
            print(f"Selected value for combo is: {text_value}")

    def send_keys(self, element, arg):
        """
        [Internal]

        Clicks two times on the Selenium element.

        :param element: Selenium element
        :type element: Selenium object
        :param arg: Text or Keys to be sent to the element
        :type arg: str or selenium.webdriver.common.keys

        Usage:

        >>> #Defining the element:
        >>> element = lambda: self.driver.find_element_by_id("example_id")
        >>> #Calling the method with a string
        >>> self.send_keys(element(), "Text")
        >>> #Calling the method with a Key
        >>> self.send_keys(element(), Keys.ENTER)
        """
        try:
            element.send_keys("")
            element.click()
            element.send_keys(arg)
        except Exception:
            actions = ActionChains(self.driver)
            actions.move_to_element(element)
            actions.send_keys("")
            actions.click()
            actions.send_keys(arg)
            actions.perform()

    def search_stack(self, function):
        """
        Returns True if passed function is present in the call stack.

        :param function: Name of the function
        :type function: str

        :return: Boolean if passed function is present or not in the call stack.
        :rtype: bool

        Usage:

        >>> # Calling the method:
        >>> is_present = self.search_stack("MATA020")
        """
        return len(
            list(filter(lambda x: x.function == function,
                        inspect.stack()))) > 0

    def set_element_focus(self, element):
        """
        [Internal]

        Sets focus on element.

        :param element: Selenium element
        :type element: Selenium object

        Usage:

        >>> #Defining the element:
        >>> element = lambda: self.driver.find_element_by_id("example_id")
        >>> #Calling the method
        >>> text = self.set_element_focus(element())
        """
        self.driver.execute_script("window.focus(); arguments[0].focus();",
                                   element)

    def web_scrap(self,
                  term,
                  scrap_type=enum.ScrapType.TEXT,
                  optional_term=None,
                  label=False,
                  main_container=None):
        """
        [Internal]

        Returns a BeautifulSoup object list based on the search parameters.

        Does not support ScrapType.XPATH as scrap_type parameter value.

        :param term: The first search term. A text or a selector
        :type term: str
        :param scrap_type: The type of webscraping. - **Default:** enum.ScrapType.TEXT
        :type scrap_type: enum.ScrapType.
        :param optional_term: The second search term. A selector used in MIXED webscraping. - **Default:** None
        :type optional_term: str
        :param label: If the search is based on a label near the element. - **Default:** False
        :type label: bool
        :param main_container: The selector of a container element that has all other elements. - **Default:** None
        :type main_container: str

        :return: List of BeautifulSoup4 elements based on search parameters.
        :rtype: List of BeautifulSoup4 objects

        Usage:

        >>> #All buttons
        >>> buttons = self.web_scrap(term="button", scrap_type=enum.ScrapType.CSS_SELECTOR)
        >>> #----------------#
        >>> #Elements that contain the text "Example"
        >>> example_elements = self.web_scrap(term="Example")
        >>> #----------------#
        >>> #Elements with class "my_class" and text "my_text"
        >>> elements = self.web_scrap(term="my_text", scrap_type=ScrapType.MIXED, optional_term=".my_class")
        """
        try:
            endtime = time.time() + 60
            container = None
            while (time.time() < endtime and container is None):
                soup = self.get_current_DOM()

                if self.config.log_file:
                    with open(
                            f"{term + str(scrap_type) + str(optional_term) + str(label) + str(main_container) + str(random.randint(1, 101)) }.txt",
                            "w") as text_file:
                        text_file.write(f" HTML CONTENT: {str(soup)}")

                container_selector = self.base_container
                if (main_container is not None):
                    container_selector = main_container

                containers = self.zindex_sort(soup.select(container_selector),
                                              reverse=True)

                container = next(iter(containers), None)

            if container is None:
                raise Exception("Couldn't find container")

            if (scrap_type == enum.ScrapType.TEXT):
                if label:
                    return self.find_label_element(term, container)
                else:
                    return list(
                        filter(lambda x: term.lower() in x.text.lower(),
                               container.select("div > *")))
            elif (scrap_type == enum.ScrapType.CSS_SELECTOR):
                return container.select(term)
            elif (scrap_type == enum.ScrapType.MIXED
                  and optional_term is not None):
                return list(
                    filter(lambda x: term.lower() in x.text.lower(),
                           container.select(optional_term)))
            elif (scrap_type == enum.ScrapType.SCRIPT):
                script_result = self.driver.execute_script(term)
                return script_result if isinstance(script_result, list) else []
            else:
                return []
        except Exception as e:
            self.log_error(str(e))

    def zindex_sort(self, elements, reverse=False):
        """
        [Internal]

        Sorts list of BeautifulSoup elements based on z-index style attribute.

        Only works if elements have Style attribute.

        :param elements: BeautifulSoup element list
        :type elements: List of BeautifulSoup objects
        :param reverse: Boolean value if order should be reversed or not. - **Default:** False
        :type reverse: bool

        :return: List of sorted BeautifulSoup elements based on zindex.
        :rtype: List of BeautifulSoup objects

        Usage:

        >>> #Defining the element list:
        >>> soup = self.get_current_DOM()
        >>> elements = soup.select("div")
        >>> #Calling the method
        >>> self.zindex_sort(elements, True)
        """
        elements.sort(key=lambda x: self.search_zindex(x), reverse=reverse)
        return elements

# User Methods

    def AssertFalse(self):
        """
        Defines that the test case expects a False response to pass

        Usage:

        >>> #Calling the method
        >>> self.AssertFalse()
        """
        self.assert_result(False)

    def AssertTrue(self):
        """
        Defines that the test case expects a True response to pass

        Usage:

        >>> #Calling the method
        >>> self.AssertTrue()
        """
        self.assert_result(True)

    def TearDown(self):
        """
        Closes the webdriver and ends the test case.

        Usage:

        >>> #Calling the method
        >>> self.TearDown()
        """
        self.driver.close()