Ejemplo n.º 1
0
class Cleanup:
    def __init__(self, days: float = 31):
        self.managedPaths = ManagedPaths()
        self.threshold = days

    def clean_logs(self):
        logger.info(f"Removing logs older than {str(self.threshold)} days")
        logs_dir = self.managedPaths.getLogfilePath()
        files = Path(logs_dir).glob('**/*')
        removed = []
        for file in files:
            timedelta = datetime.now() - datetime.fromtimestamp(DownloadFolderMonitoring.creation_date(file))
            if timedelta.total_seconds()/86400 > self.threshold:
                try:
                    os.remove(str(file))
                    removed.append(file)
                except:
                    logger.debug(f"Cannot remove {str(file)}")
                    continue
                logger.debug(f"{str(file)} deleted")
        if len(removed) == 0:
            logger.info(f"No log file older than {str(self.threshold)} days found")
        else:
            logger.info(f"{str(len(removed))} Log file deleted")

    def clean_screenshots(self):
        logger.info(f"Removing screenshots older than {str(self.threshold)} days")
        ss_dir = self.managedPaths.getOrSetScreenshotsPath()
        files = Path(ss_dir).glob('**/*')
        removed = []
        for file in files:
            timedelta = datetime.now() - datetime.fromtimestamp(DownloadFolderMonitoring.creation_date(file))
            if timedelta.total_seconds()/86400 > self.threshold:
                try:
                    os.remove(str(file))
                    removed.append(file)
                except:
                    logger.debug(f"Cannot remove {str(file)}")
                    continue
                logger.debug(f"{str(file)} deleted")
        if len(removed) == 0:
            logger.info(f"No Screenshot older than {str(self.threshold)} days found")
        else:
            logger.info(f"{str(len(removed))} Screenshot deleted")

    def clean_downloads(self):
        logger.info(f"Removing downloads older than {str(self.threshold)} days")
        downloads_dir = self.managedPaths.getOrSetAttachmentDownloadPath()
        files = Path(downloads_dir).glob('**/*')
        removed = []
        for file in files:
            if file.is_file():
                timedelta = datetime.now() - datetime.fromtimestamp(DownloadFolderMonitoring.creation_date(file))
                if timedelta.total_seconds()/86400 > self.threshold:
                    try:
                        os.remove(str(file))
                        removed.append(file)
                    except:
                        logger.debug(f"Cannot remove {str(file)}")
                        continue
                    logger.debug(f"{str(file)} deleted")
        if len(removed) == 0:
            logger.info(f"No downloads older than {str(self.threshold)} days found")
        else:
            logger.info(f"{str(len(removed))} downloads deleted")
        sleep(1)
        logger.info("Removing empty folders inside download folder.")
        files = Path(downloads_dir).glob('**/*')
        removed = []
        for file in files:
            if file.is_dir():
                if len(os.listdir(str(file))) == 0:
                    try:
                        os.rmdir(str(file))
                        removed.append(file)
                    except:
                        logger.debug(f"Cannot remove folder {str(file)}")
                        continue
                    logger.debug(f"{str(file)} folder deleted")
        if len(removed) == 0:
            logger.info(f"No empty folder found inside downloads")
        else:
            logger.info(f"{str(len(removed))} empty folder deleted")

    def clean_reports(self):
        #
        # cleanup reports
        #

        logger.info(f"Removing reports older than {str(self.threshold)} days")
        reports_dir = self.managedPaths.getOrSetReportPath()
        files = Path(reports_dir).glob('**/*')
        removed = []
        for file in files:
            timedelta = datetime.now() - datetime.fromtimestamp(DownloadFolderMonitoring.creation_date(file))
            if timedelta.total_seconds()/86400 > self.threshold:
                try:
                    os.remove(str(file))
                    removed.append(file)
                except:
                    logger.debug(f"Cannot remove {str(file)}")
                    continue
                logger.debug(f"{str(file)} deleted")
        if len(removed) == 0:
            logger.info(f"No report older than {str(self.threshold)} days found")
        else:
            logger.info(f"{str(len(removed))} reports deleted")

    def clean_all(self):
        self.clean_logs()
        self.clean_screenshots()
        self.clean_downloads()
        self.clean_reports()
Ejemplo n.º 2
0
class BrowserDriver:
    """
    The main class for baangt-Elements to interact with a browser.
    Main Methods:
    - createNewBrowser: Create one instance of a Browser
    - findBy*-Methods: e.g. findByAndClick
    - URL-Methods: To navigate to an URL
    - handleIframe and handleWindow: To navigate between Windows (=tabs) and Iframes
    - javaScript: to pass JS directly to the browser
    - takeScreenshot: yes, that.
    """
    def __init__(self, timing=None, screenshotPath=None, statistics=None):
        self.iFrame = None
        self.element = None
        self.browserData = BrowserDriverData(
            locatorType=None,
            locator=None,
            driver=webDrv.BROWSER_DRIVERS[GC.BROWSER_FIREFOX])
        self.slowExecution = False
        self.slowExecutionTimeoutInSeconds = 1
        self.downloadFolder = None
        self.downloadFolderMonitoring = None
        self.randomProxy = None
        self.zoomFactorDesired = None  # Desired zoom factor for this page
        self.browserName = None
        # Reference to Selenium "HTML" in order to track page changes. It is set on every interaction with the page
        self.html = None
        self.managedPaths = ManagedPaths()
        self.statistics = Statistic()

        if timing:
            self.timing = timing
            self.takeTime = timing.takeTime
        else:
            self.timing = Timing()
            self.takeTime = self.timing.takeTime

        if not screenshotPath or screenshotPath == "":
            self.screenshotPath = self.managedPaths.getOrSetScreenshotsPath()
        else:
            self.screenshotPath = screenshotPath

    def sleep(self, seconds):
        time.sleep(seconds)

    def createNewBrowser(self,
                         mobileType=None,
                         mobileApp=None,
                         desired_app=None,
                         mobile_app_setting=None,
                         browserName=GC.BROWSER_FIREFOX,
                         desiredCapabilities={},
                         randomProxy=None,
                         **kwargs):
        """
        Will find the specified executables of the desired browser and start it with the given capabilities.

        @param browserName: one of GC_BROWSER_*-Browsernames, e.g. GC_BROWSER_FIREFOX
        @param desiredCapabilities: DICT of desiredCapabilities for this browser
        @param kwargs: Currently (Jan2020) not used
        """
        self.takeTime("Browser Start")
        self.randomProxy = randomProxy
        self.browserName = browserName
        self.browserProcessID = []
        lCurPath = Path(self.managedPaths.getOrSetDriverPath())

        if browserName in webDrv.BROWSER_DRIVERS:

            browserProxy = kwargs.get('browserProxy')
            browserInstance = kwargs.get('browserInstance', 'unknown')

            if utils.anything2Boolean(mobileType):
                self.browserData.driver = self._mobileConnectAppium(
                    browserName, desired_app, mobileApp, mobile_app_setting)
            elif GC.BROWSER_FIREFOX == browserName:
                self.browserData.driver = self._browserFirefoxRun(
                    browserName, lCurPath, browserProxy, randomProxy,
                    desiredCapabilities)
                helper.browserHelper_startBrowsermobProxy(
                    browserName=browserName,
                    browserInstance=browserInstance,
                    browserProxy=browserProxy)
                self.browserProcessID.append(
                    self.browserData.driver.capabilities.get("moz:processID"))
            elif GC.BROWSER_CHROME == browserName:
                self.browserData.driver = self._browserChromeRun(
                    browserName, lCurPath, browserProxy, randomProxy,
                    desiredCapabilities)
                helper.browserHelper_startBrowsermobProxy(
                    browserName=browserName,
                    browserInstance=browserInstance,
                    browserProxy=browserProxy)
                try:
                    port = self.browserData.driver.capabilities[
                        'goog:chromeOptions']["debuggerAddress"].split(":")[1]
                    fp = os.popen(f"lsof -nP -iTCP:{port} | grep LISTEN")
                    self.browserProcessID.append(
                        int(fp.readlines()[-1].split()[1]))
                except Exception as ex:
                    logger.info(ex)
            elif GC.BROWSER_EDGE == browserName:
                self.browserData.driver = webDrv.BROWSER_DRIVERS[browserName](
                    executable_path=helper.
                    browserHelper_findBrowserDriverPaths(GC.EDGE_DRIVER))
            elif GC.BROWSER_SAFARI == browserName:
                # SAFARI doesn't provide any options, but desired_capabilities.
                # Executable_path = the standard safaridriver path.
                if len(desiredCapabilities) == 0:
                    desiredCapabilities = {}
                self.browserData.driver = webDrv.BROWSER_DRIVERS[browserName](
                    desired_capabilities=desiredCapabilities)
            elif GC.BROWSER_REMOTE == browserName:
                self.browserData.driver = webDrv.BROWSER_DRIVERS[browserName](
                    options=webDrv.webdriver_createBrowserOptions(
                        browserName=browserName,
                        desiredCapabilities=desiredCapabilities),
                    command_executor=GC.REMOTE_EXECUTE_URL,
                    desired_capabilities=desiredCapabilities)
            else:
                logger.critical(
                    f"Browsername not found: {browserName}. Cancelling test run"
                )
                raise SystemError(
                    f"Browsername not found: {browserName}. Cancelling test run"
                )
        elif GC.BROWSER_REMOTE_V4 == browserName:
            desired_capabilities, seleniumGridIp, seleniumGridPort = helper.browserHelper_setSettingsRemoteV4(
                desiredCapabilities)

            if desired_capabilities['browserName'] == 'firefox':
                browserExecutable = helper.browserHelper_getBrowserExecutable(
                    GC.BROWSER_FIREFOX)
                self._downloadDriverCheck(browserExecutable, lCurPath,
                                          GC.BROWSER_FIREFOX)
            elif desired_capabilities['browserName'] == 'chrome':
                browserExecutable = helper.browserHelper_getBrowserExecutable(
                    GC.BROWSER_CHROME)
                self._downloadDriverCheck(browserExecutable, lCurPath,
                                          GC.BROWSER_CHROME)

            serverUrl = 'http://' + seleniumGridIp + ':' + seleniumGridPort
            self.browserData.driver = webDrv.BROWSER_DRIVERS[
                GC.BROWSER_REMOTE](command_executor=serverUrl,
                                   desired_capabilities=desiredCapabilities)
        else:
            raise SystemExit("Browsername unknown")

        if self.downloadFolder:
            self.downloadFolderMonitoring = DownloadFolderMonitoring(
                self.downloadFolder)

        self.takeTime("Browser Start")

    def _downloadDriverCheck(self, executable, lCurPath, browserName):
        lCurPath = lCurPath.joinpath(executable)

        if not (os.path.isfile(str(lCurPath))):
            self.downloadDriver(browserName)

    def _browserChromeRun(self, browserName, lCurPath, browserProxy,
                          randomProxy, desiredCapabilities):
        executable = helper.browserHelper_getBrowserExecutable(browserName)
        self._downloadDriverCheck(executable, lCurPath, browserName)

        lOptions = webDrv.webdriver_createBrowserOptions(
            browserName=browserName,
            desiredCapabilities=desiredCapabilities,
            browserMobProxy=browserProxy,
            randomProxy=randomProxy)

        self.downloadFolder = webDrv.getDownloadFolderFromChromeOptions(
            options=lOptions)

        return webDrv.BROWSER_DRIVERS[browserName](
            chrome_options=lOptions,
            executable_path=helper.browserHelper_findBrowserDriverPaths(
                executable),
            service_log_path=os.path.join(self.managedPaths.getLogfilePath(),
                                          'chromedriver.log'))

    def _browserFirefoxRun(self, browserName, lCurPath, browserProxy,
                           randomProxy, desiredCapabilities):
        executable = helper.browserHelper_getBrowserExecutable(browserName)
        self._downloadDriverCheck(executable, lCurPath, browserName)

        profile = webDrv.webdriver_setFirefoxProfile(browserProxy, randomProxy)
        self.downloadFolder = webDrv.getDownloadFolderFromProfile(profile)
        logger.debug(f"Firefox Profile as follows:{profile.userPrefs}")

        return webDrv.BROWSER_DRIVERS[browserName](
            options=webDrv.webdriver_createBrowserOptions(
                browserName=browserName,
                desiredCapabilities=desiredCapabilities),
            executable_path=helper.browserHelper_findBrowserDriverPaths(
                executable),
            firefox_profile=profile,
            service_log_path=os.path.join(self.managedPaths.getLogfilePath(),
                                          'geckodriver.log'))

    @staticmethod
    def _mobileConnectAppium(browserName, desired_app, mobileApp,
                             mobile_app_setting):
        validSettings = False
        desired_cap = desired_app

        if desired_app[GC.MOBILE_PLATFORM_NAME] == "Android":
            validSettings = True
            if utils.anything2Boolean(mobileApp):
                desired_cap['app'] = mobile_app_setting[GC.MOBILE_APP_URL]
                desired_cap['appPackage'] = mobile_app_setting[
                    GC.MOBILE_APP_PACKAGE]
                desired_cap['appActivity'] = mobile_app_setting[
                    GC.MOBILE_APP_ACTIVITY]
            else:
                desired_cap['browserName'] = browserName
                desired_cap['chromedriverExecutable'] = mobile_app_setting[
                    GC.MOBILE_APP_BROWSER_PATH]
                desired_cap['noReset'] = False

        elif desired_app[GC.MOBILE_PLATFORM_NAME] == "iOS":
            validSettings = True
            if utils.anything2Boolean(mobileApp):
                desired_cap['automationName'] = 'XCUITest'
                desired_cap['app'] = mobile_app_setting[GC.MOBILE_APP_URL]
            else:
                desired_cap['browserName'] = 'safari'

        if validSettings:
            return webDrv.BROWSER_DRIVERS[GC.BROWSER_APPIUM](
                GC.REMOTE_EXECUTE_URL, desired_cap)
        else:
            return None

    def closeBrowser(self):
        self.statistics.update_teststep()
        try:
            if self.browserData.driver:
                try:
                    if len(self.browserProcessID) > 0:
                        for bpid in self.browserProcessID:
                            os.kill(bpid, signal.SIGINT)
                except:
                    pass
                self.browserData.driver.close()
                self.browserData.driver.quit()
        except Exception as ex:
            logger.info(ex)
            pass  # If the driver is already dead, it's fine.
        self.browserData.driver = None

    def refresh(self):
        self.browserData.driver.execute_script("window.location.reload()")

    def takeScreenshot(self, screenShotPath=None):
        driver = self.browserData.driver
        # Filename must have ".png" inside
        lFile = str(uuid.uuid4()) + ".png"

        if screenShotPath:
            lFile = Path(screenShotPath).joinpath(lFile)
        else:
            lFile = Path(self.screenshotPath).joinpath(lFile)

        try:
            lFile = str(lFile)
            driver.save_screenshot(lFile)
            helper.browserHelper_log(logging.DEBUG,
                                     f"Stored Screenshot: {lFile}",
                                     self.browserData)
        except Exception as e:
            helper.browserHelper_log(logging.INFO,
                                     f"Screenshot not possible. Error: {e}",
                                     self.browserData)
            lFile = None

        return lFile

    def handleIframe(self, iframe=None):
        """
        Give an IFRAME and it will try to go into.
        If you're inside an iframe it will go out of the iframe
        """
        self.statistics.update_teststep()
        if iframe:
            self.browserData.locatorType = "XPATH"
            self.browserData.locator = iframe
            helper.browserHelper_log(logging.DEBUG, "Going into Iframe: ",
                                     self.browserData, **{"iframe": iframe})
            # frame_to_be_availble_and_switch_to_it doesn't work.
            mustEnd = time.time() + 30
            while time.time() < mustEnd:
                try:
                    self.browserData.driver.switch_to.default_content()
                    self.iFrame = self.browserData.driver.switch_to.frame(
                        iframe)
                    break
                except WebDriverException as e:
                    helper.browserHelper_log(
                        logging.DEBUG,
                        f"IFrame {iframe} not there yet - waiting 1 second",
                        self.browserData)
                    time.sleep(1)

            if time.time() > mustEnd:
                raise TimeoutError

        elif self.iFrame:
            helper.browserHelper_log(logging.DEBUG,
                                     f"Leaving Iframe: {self.iFrame}",
                                     self.browserData)
            self.browserData.driver.switch_to.default_content()
            self.iFrame = None
        else:
            # TODO add exception, this code should never be reached
            pass

    def handleWindow(self, windowNumber=None, function=None, timeout=20):
        """
        Interations with Windows (=BrowserTabs).

        @param windowNumber: Number of the windowHandle inside this browser session (0 = startwindow(=Tab), 1=Next window
        @param function: "CLOSE", "CLOSEALL"
        """
        self.statistics.update_teststep()
        if function:
            if "close" == function.lower():
                self.browserData.driver.close()
                self.browserData.driver.switch_to.window(
                    self.browserData.driver.window_handles[0])
            elif "closeall" in function.lower():
                exceptHandles = function.lower().replace("closeall", "")
                exceptHandles = exceptHandles.replace("-", "")
                # WindowHandles based on 0.. Value "let 2 windows open" means to close everything except 0 and 1:
                exceptHandles = int(exceptHandles.strip()) - 1
                try:
                    len(self.browserData.driver.window_handles)
                except BaseException as e:
                    logger.error(
                        f"Tried to get amount of windows. Threw error {e}. Most probably browser crashed"
                    )
                    raise Exceptions.baangtTestStepException(
                        f"Tried to get amount of windows. "
                        f"Threw error {e}. Most probably browser crashed")
                for windowHandle in self.browserData.driver.window_handles[
                        -1:exceptHandles:-1]:
                    try:
                        self.browserData.driver.switch_to.window(windowHandle)
                        self.browserData.driver.close()
                    except NoSuchWindowException as e:
                        # If the window is already closed, it's fine. Don't do anything
                        pass
                try:
                    self.browserData.driver.switch_to.window(
                        self.browserData.driver.window_handles[exceptHandles])
                except IndexError as e:
                    raise Exceptions.baangtTestStepException(
                        f"Seems like the browser crashed. Main-Window lost")
            else:
                # TODO Wrong function, add exception
                pass
        else:
            success = False
            duration = 0
            while not success and duration < timeout:
                try:
                    self.browserData.driver.switch_to.window(
                        self.browserData.driver.window_handles[windowNumber])
                    success = True
                    continue
                except Exception as e:
                    logger.debug(
                        f"Tried to switch to Window {windowNumber} but it's not there yet"
                    )

                time.sleep(1)
                duration += 1

            if not success:
                raise Exceptions.baangtTestStepException(
                    f"Window {windowNumber} doesn't exist after timeout {timeout}"
                )

    def findByAndWaitForValue(self,
                              id=None,
                              css=None,
                              xpath=None,
                              class_name=None,
                              iframe=None,
                              timeout=20,
                              optional=False):
        """

        @param id: ID of the element
        @param css: CSS-Locator
        @param xpath: XPATH-Locator
        @param class_name: Class-Name
        @param iframe: Iframe to use (use only if changed. If you set an iframe before, you don't need to set it again!)
        @param timeout: Timeout in Seconds before raising an error or returning back (depending on "optional")
        @param optional: If set to "True" and the operation can not be executed, just a log entry is written but no error raised
        @return: the text of the element, if element was found
        """
        self.statistics.update_teststep()
        self.element = None
        returnValue = None
        start = time.time()
        duration = 0
        retry = True

        while retry and duration < timeout:
            self.element, self.html = self.findBy(id=id,
                                                  css=css,
                                                  xpath=xpath,
                                                  class_name=class_name,
                                                  iframe=iframe,
                                                  timeout=timeout / 3,
                                                  optional=optional)
            time.sleep(0.5)
            duration = time.time() - start

            if self.element:
                try:
                    if len(self.element.text) > 0:
                        returnValue = self.element.text.strip()
                    elif self.element.tag_name == 'input':
                        #  element is of type <input />
                        returnValue = self.element.get_property(
                            'value').strip()
                except Exception as e:
                    logger.debug(
                        f"Exception during findByAndWaitForValue, but continuing {str(e)}, "
                        f"Locator: {self.browserData.locatorType} = {self.browserData.locator}"
                    )
            else:
                logger.info(
                    f"Couldn't find value for element {self.browserData.locatorType}:{self.browserData.locator}"
                )

            if returnValue and len(returnValue.strip()) > 0:
                return returnValue

        return returnValue

    def findByAndSetText(self,
                         id=None,
                         css=None,
                         xpath=None,
                         class_name=None,
                         value=None,
                         iframe=None,
                         timeout=60,
                         optional=False):
        """
        Please see documentation in findBy and __doSomething
        """
        self.element, self.html = self.findBy(id=id,
                                              css=css,
                                              xpath=xpath,
                                              class_name=class_name,
                                              iframe=iframe,
                                              timeout=timeout,
                                              optional=optional)
        if not self.element:
            return False

        return webDrv.webdriver_doSomething(GC.CMD_SETTEXT,
                                            self.element,
                                            value=value,
                                            timeout=timeout,
                                            optional=optional,
                                            browserData=self.browserData)

    def slowExecutionToggle(self, newSlowExecutionWaitTimeInSeconds=None):
        """
        SlowExecution can be set in globals or by the teststep. It's intended use is debugging or showcasing a testcases
        functionality.

        @param newSlowExecutionWaitTimeInSeconds: Optional. If set, it will change the default value of WaitTime, when SlowExecution is active
        @return: Returns the state of sloeExecution toggle after toggling was done.
        """

        if newSlowExecutionWaitTimeInSeconds:
            self.slowExecutionTimeoutInSeconds = newSlowExecutionWaitTimeInSeconds

        return not self.slowExecution

    def findByAndSetTextIf(self,
                           id=None,
                           css=None,
                           xpath=None,
                           class_name=None,
                           value=None,
                           iframe=None,
                           timeout=60,
                           optional=False):
        """
        Helper function to not have to write:
        If <condition>:
            findByAndSetText(locator)

        instead use:
        findByAndSetTextIf(locator, value).

        If value is evaluated into "True" the Text is set.

        """

        if self._isValidKeyValue(value):
            return self.findByAndSetText(id=id,
                                         css=css,
                                         xpath=xpath,
                                         class_name=class_name,
                                         value=value,
                                         iframe=iframe,
                                         timeout=timeout,
                                         optional=optional)
        else:
            return False

    def findByAndSetTextValidated(self,
                                  id=None,
                                  css=None,
                                  xpath=None,
                                  class_name=None,
                                  value=None,
                                  iframe=None,
                                  timeout=60,
                                  retries=5):
        """
        This is a method not recommended to be used regularly. Sometimes (especially with Angular Frontends) it gets
        pretty hard to set a value into a field. Chrome, but also FF will show the value, but the DOM will not have it.
        Ths Method should be your last ressort. Here we try <retries> time to set a value. Then we read the element again
        and compare value to what we'd expect. If value is different and we're less than <retries>-Times, we'll try again.
        """

        tries = 0

        self.element, self.html = self.findBy(id=id,
                                              css=css,
                                              xpath=xpath,
                                              class_name=class_name,
                                              iframe=iframe,
                                              timeout=timeout)

        while self.element.text != value and self.element.get_property(
                "value") != value and tries < retries:
            helper.browserHelper_log(
                logging.DEBUG,
                f"Verified trying of SetText - iteration {tries} of {retries}",
                self.browserData)

            self.findByAndForceText(id=id,
                                    css=css,
                                    xpath=xpath,
                                    class_name=class_name,
                                    iframe=iframe,
                                    value=value,
                                    timeout=timeout)

            self.element, self.html = self.findBy(id=id,
                                                  css=css,
                                                  xpath=xpath,
                                                  class_name=class_name,
                                                  iframe=iframe,
                                                  timeout=timeout)

            tries += 1

    def submit(self):
        """
        Used for forms to call the standard submit-function (similar to pressing "Enter" in Dialogue)
        @return:
        """
        self.statistics.update_teststep()
        self.element.submit()

    def findByAndClick(self,
                       id=None,
                       css=None,
                       xpath=None,
                       class_name=None,
                       iframe=None,
                       timeout=20,
                       optional=False):
        """
        Execute a Click on an element identified by it's locator.
        @return wasSuccessful says, whether the element was found.
        """

        self.element, self.html = self.findBy(id=id,
                                              css=css,
                                              xpath=xpath,
                                              class_name=class_name,
                                              iframe=iframe,
                                              timeout=timeout,
                                              optional=optional)

        if not self.element:
            logger.debug("findBy didn't work in findByAndClick")
            return False
        else:
            return webDrv.webdriver_doSomething(GC.CMD_CLICK,
                                                self.element,
                                                timeout=timeout,
                                                optional=optional,
                                                browserData=self.browserData)

    def confirmAlertIfAny(self):
        self.statistics.update_teststep()
        try:
            self.browserData.driver.switch_to().alert().accept()
        except Exception as e:
            pass

    @staticmethod
    def _isValidKeyValue(value):
        isValid = False
        if not value:
            pass
        elif len(str(value)) == 0 or str(value) == "0":
            pass
        else:
            isValid = True
        return isValid

    def findByAndClickIf(self,
                         id=None,
                         css=None,
                         xpath=None,
                         class_name=None,
                         iframe=None,
                         timeout=60,
                         value=None,
                         optional=False):
        """
        Convenience method to not have to write:
        if <condition>:
            findByAndClick(locator)

        instead write:
        findByAndClickIf(locator, value).

        If value is evaluated to "True", the click-event is executed.
        """
        if self._isValidKeyValue(value):
            return self.findByAndClick(id=id,
                                       css=css,
                                       xpath=xpath,
                                       class_name=class_name,
                                       iframe=iframe,
                                       timeout=timeout,
                                       optional=optional)
        else:
            return False

    def findByAndForceText(self,
                           id=None,
                           css=None,
                           xpath=None,
                           class_name=None,
                           value=None,
                           iframe=None,
                           timeout=60,
                           optional=False):
        """
        Convenience Method. Please see documentation in findBy and __doSomething.

        """

        self.element, self.html = self.findBy(id=id,
                                              css=css,
                                              xpath=xpath,
                                              class_name=class_name,
                                              iframe=iframe,
                                              timeout=timeout,
                                              optional=optional)

        if not self.element:
            return False

        return webDrv.webdriver_doSomething(GC.CMD_FORCETEXT,
                                            self.element,
                                            value=value,
                                            timeout=timeout,
                                            optional=optional,
                                            browserData=self.browserData)

    def findByAndForceViaJS(self,
                            id=None,
                            css=None,
                            xpath: str = None,
                            class_name=None,
                            value=None,
                            iframe=None,
                            timeout=60,
                            optional=False):
        """
        Identifies the object via JS and set's the desired value via JS
        """
        # element, html = self.findBy(id=id ,css=css, xpath=xpath, class_name=class_name, iframe=iframe,
        # timeout=timeout, optional=optional)
        # didn't work to give the element to JavaScript-method

        xpath = xpath.replace('"', "'")
        xpath = xpath.replace("'", "\\'")
        lJSText = "\n".join([
            f"var zzbaangt = document.evaluate('{xpath}', document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);",
            "if (zzbaangt.snapshotLength > 0) { ",
            f"zzbaangt[0].value='{value}';", "};", f""
        ])
        logger.debug(f"Setting element using JS-Text: {lJSText}")

        # lJSText = f"arguments[0].value='{value}';" --> Didn't work with Element.

        self.javaScript(lJSText)

    def setBrowserWindowSize(self, browserWindowSize: str):
        """
        Resized the browser Window to a fixed size
        :param browserWindowSize: String with Widht/Height or Width;Height or Width,height or width x height
               If you want also with leading --
        :return: False, if browser wasn't reset,
                 size-Dict when resize worked.
        """
        self.statistics.update_teststep()
        lIntBrowserWindowSize = browserWindowSize.replace("-", "").strip()
        lIntBrowserWindowSize = lIntBrowserWindowSize.replace(";", "/")
        lIntBrowserWindowSize = lIntBrowserWindowSize.replace(",", "/")
        lIntBrowserWindowSize = lIntBrowserWindowSize.replace("x", "/")
        lIntBrowserWindowSize = lIntBrowserWindowSize.replace("*", "/")
        validSize = False

        try:
            width = int(lIntBrowserWindowSize.split("/")[0])
            height = int(lIntBrowserWindowSize.split("/")[1])
        except KeyError as e:
            logger.warning(
                f"Called with wrong setting: {browserWindowSize}. Won't resize browser "
                f"Can't determine Width/Height.")
        except ValueError as e:
            logger.warning(
                f"Called with wrong setting: {browserWindowSize}. Won't resize browser "
                f"Something seems not numeric before conversion: {lIntBrowserWindowSize}"
            )

        try:
            if width == 0 or height == 0:
                logger.warning(
                    f"Called with wrong setting: {browserWindowSize}. Won't resize browser. Can't be 0"
                )
            else:
                validSize = True
        except:
            pass

        if validSize:
            self.browserData.driver.set_window_size(width, height)
            size = self.browserData.driver.get_window_size()
            logger.debug(
                f"Resized browser window to width want/is: {width}/{size['width']}, "
                f"height want/is: {height}/{size['height']}")
        else:
            size = False

        return size

    def findNewFiles(self):
        """
        Returns a list of new files from downloadFolderMonitoring since the last call

        :return: List of Files since last call
        """
        self.statistics.update_teststep()
        l_list = self.downloadFolderMonitoring.getNewFiles()
        return l_list

    @staticmethod
    def _setLocator(id, css, xpath, class_name, browserData):
        browserData.locatorType = None
        browserData.locator = None
        if xpath:
            browserData.locatorType = By.XPATH
            browserData.locator = xpath
        elif css:
            browserData.locatorType = By.CSS_SELECTOR
            browserData.locator = css
        elif class_name:
            browserData.locatorType = By.CLASS_NAME
            browserData.locator = class_name
        elif id:
            browserData.locatorType = By.ID
            browserData.locator = id
        return browserData

    def findBy(self,
               id=None,
               css=None,
               xpath=None,
               class_name=None,
               iframe=None,
               timeout=60,
               loggingOn=True,
               optional=False):
        """
        chose any single locator (ID, CSS, XPATH, CLASS_NAME) to identify an element within the page. if slowExectuion
        is set, we'll pause for slowExecutionTimeoutInSeconds.

        @param id: ID of the element
        @param css: CSS-Locator
        @param xpath: XPATH
        @param class_name: Class-Name
        @param iframe: Name of an Iframe. Use only, if you didn't set the Iframe previously already!
        @param timeout: How many seconds shall we try/retry, default = 60 Seconds
        @param loggingOn: Shall this request be logged? Default = Yes
        @param optional: If set to true and within Timeout we can't find the element, we just return this info. If set to False (=default), an Exception is raised
        @return: True if element was located, False if element couldn't be found.
        """
        self.statistics.update_teststep()

        if self.slowExecution:
            time.sleep(self.slowExecutionTimeoutInSeconds)

        if iframe:
            self.handleIframe(iframe)

        # Set class variables for potential logging of problems.
        self.browserData = self._setLocator(id, css, xpath, class_name,
                                            self.browserData)

        if loggingOn:
            logger.debug(
                f"Locating Element {self.browserData.locatorType} = {self.browserData.locator}"
            )

        self.element, self.html = webDrv.webdriver_tryAndRetry(
            self.browserData, timeout=timeout, optional=optional)

        if not self.element and not optional:
            raise Exceptions.baangtTestStepException(
                f"Element {self.browserData.locatorType} = {self.browserData.locator} could not be found "
                f"within timeout of {timeout}")
        return self.element, self.html

    def getURL(self):
        """

        @return: the current URL/URI of the current Tab of the current Browser
        """
        self.statistics.update_teststep()
        return self.browserData.driver.current_url

    def findWaitNotVisible(self,
                           css=None,
                           xpath=None,
                           id=None,
                           timeout=90,
                           optional=False):
        """
        You'd use this method when you wait for an element to disappear, for instance Angular Spinner or a popup
        to disapear before you continue with your script in the main screen.

        """
        self.statistics.update_teststep()
        logger.debug(
            f"Waiting for Element to disappear: XPATH:{xpath}, timeout: {timeout}"
        )
        time.sleep(0.5)

        stillHere = True
        elapsed = 0
        begin = time.time()

        while stillHere and elapsed < timeout:
            try:
                if xpath:
                    self.element = self.browserData.driver.find_element_by_xpath(
                        xpath)
                elif id:
                    self.element = self.browserData.driver.find_element_by_id(
                        id)
                elif css:
                    self.element = self.browserData.driver.find_element_by_css_selector(
                        css)
                time.sleep(0.2)
                elapsed = time.time() - begin
            except Exception as e:
                # Element gone - exit
                stillHere = False
                helper.browserHelper_log(
                    logging.DEBUG,
                    f"Element was gone after {format(elapsed, '.2f')} seconds",
                    self.browserData)

        if not stillHere:
            raise Exceptions.baangtTestStepException(
                f"Element still here after {timeout} seconds. Locator: xpath={xpath}, id={id}"
            )

        return stillHere

    def checkLinks(self):
        """
        For the current page we'll check all links and return result in format
        <status_code> <link_that_was_checked>

        :return: List of checked links
        """
        self.statistics.update_teststep()
        lResult = []
        links = self.browserData.driver.find_elements_by_css_selector("a")
        logger.debug(
            f"Checking links on page {self.browserData.driver.current_url}")
        for link in links:
            lHref = link.get_attribute("href")
            if not lHref:
                continue
            if lHref.startswith("mailto"):
                pass
            else:
                try:
                    r = requests.head(lHref)
                    lResult.append([r.status_code, lHref])
                    logger.debug(f"Result was: {r.status_code}, {lHref}")
                except requests.exceptions.InvalidURL as e:
                    lResult.append([
                        HTTPStatus.INTERNAL_SERVER_ERROR,
                        f"Invalid URL: {lHref}"
                    ])
                except requests.exceptions.ConnectionError as e:
                    lResult.append([
                        HTTPStatus.INTERNAL_SERVER_ERROR,
                        f"HTTP connection error: {lHref}"
                    ])
                except requests.exceptions.MissingSchema as e:
                    lResult.append([
                        HTTPStatus.INTERNAL_SERVER_ERROR,
                        f"Missing Schema - invalid URL: {lHref}"
                    ])

        return lResult

    def waitForElementChangeAfterButtonClick(self, timeout=5):
        """
        Wait for a stale element (in a good way). Stale means, that the object has changed.

        old element is in self.element
        old locator is in self.browserData.locatorType and self.browserData.locator

        :param timeout:
        :return:
        """

        self.statistics.update_teststep()

        lOldElement = self.element.id
        isValid = False
        lStartOfWaiting = time.time()
        elapsed = 0
        logger.debug("Starting")

        xpath, css, id = utils.setLocatorFromLocatorType(
            self.browserData.locatorType, self.browserData.locator)

        while not isValid and elapsed < timeout:
            self.element, self.html = self.findBy(xpath=xpath,
                                                  css=css,
                                                  id=id,
                                                  timeout=0.5,
                                                  optional=True)
            if not self.element:
                # Wonderful. Element is gone
                logger.debug(
                    "Old object is not in the page any longer, save to continue"
                )
                isValid = True
            if self.element.id != lOldElement:
                logger.debug("Old element is stale, save to continue")
                isValid = True

            time.sleep(0.2)
            elapsed = time.time() - lStartOfWaiting

        if not isValid:
            # TimeOut Return false
            logger.debug(
                "Old element equal to new element after timeout. Staleness not detected using this method"
            )

        return isValid

    def waitForPageLoadAfterButtonClick(self, timeout=5):
        """
        Problem: If the same XPATH/CSS/ID exists on both pages (the current one, where a button is clicked
                 and the next one, where we now want to interact, then it happens very often, that the element
                 is stale (because it was bound to the current page BEFORE the page-load happened.
        Solution: Wait deliberately until current self.element is stale.
        :param timout: Yeah, you guessed it. The timeout
        :return: True = New page loaded, False = The element didn't get stale within timeout
        """

        # Performance in 5 parallel Runs dropped from 06:50 to 07:51. That's 1 Minute slower
        # 60 Seconds or 10% time lost.
        # For now let it as it is. If users report that as a problem, revisit the subject and
        # e.g. find another way to understand, whether we're still on the same page or not.

        self.statistics.update_teststep()

        if not self.html:
            sys.exit(
                "Something is very wrong! self.html didn't exist when waitForPageLoadAfterButtonClick was called"
            )

        lStartOfWaiting = time.time()
        elapsed = 0
        logger.debug("Starting")

        while elapsed < timeout:
            lHTML = self.browserData.driver.find_element_by_tag_name("html")
            if lHTML != self.html:
                logger.debug("Page was reloaded")
                return True

            time.sleep(0.2)

            elapsed = time.time() - lStartOfWaiting

        logger.debug("No Page reload detected by this method")
        return False  # There was no changed HTML

    def goToUrl(self, url):
        self.statistics.update_teststep()
        helper.browserHelper_log(logging.INFO, f'GoToUrl:{url}',
                                 self.browserData)
        try:
            if self.browserName == GC.BROWSER_FIREFOX:
                self.browserData.driver.set_context("content")
            self.browserData.driver.get(url)
            self.setZoomFactor()
        except WebDriverException as e:
            # Use noScreenshot-Parameter as otherwise we'll try on a dead browser to create a screenshot
            helper.browserHelper_log(
                logging.ERROR,
                f"Webpage {url} not reached. Error was: {e}",
                self.browserData,
                cbTakeScreenshot=self.takeScreenshot)
            helper.browserHelper_setProxyError(self.randomProxy)
            raise Exceptions.baangtTestStepException
        except Exception as e:
            # Use noScreenshot-Parameter as otherwise we'll try on a dead browser to create a screenshot
            helper.browserHelper_log(logging.ERROR,
                                     f"Webpage {url} throws error {e}",
                                     self.browserData,
                                     cbTakeScreenshot=self.takeScreenshot)
            helper.browserHelper_setProxyError(self.randomProxy)
            raise Exceptions.baangtTestStepException(url, e)

    def goBack(self):
        """
        Method to go 1 step back in current tab's browse history
        @return:
        """
        self.statistics.update_teststep()
        try:
            self.javaScript("window.history.go(-1)")
        except Exception as e:
            helper.browserHelper_log(
                logging.WARNING,
                f"Tried to go back in history, didn't work with error {e}",
                self.browserData)

    def javaScript(self, jsText, *args):
        """Execute a given JavaScript in the current Session"""
        self.statistics.update_teststep()
        self.browserData.driver.execute_script(jsText, *args)

    def _zoomFirefox(self, lZoomKey, lHitKeyTimes):
        try:
            lWindow = self.browserData.driver.find_element_by_tag_name("html")
            # Reset the browser window to 100%:
            if platform.system().lower() == "darwin":
                lWindow.send_keys(keys.Keys.META + "0")
            else:
                lWindow.send_keys(keys.Keys.CONTROL + "0")

            # Now set to desired zoom factor:
            for counter in range(lHitKeyTimes):
                if platform.system().lower() == "darwin":
                    lWindow.send_keys(keys.Keys.META + lZoomKey)
                else:
                    lWindow.send_keys(keys.Keys.CONTROL + lZoomKey)

            logger.debug(
                f"Adjusted zoom factor of browserwindow to {self.zoomFactorDesired}"
            )
        except Exception as e:
            logger.debug(f"Tried to adjust zoom factor and failed: {e}")
        finally:
            self.browserData.driver.set_context("content")

    def setZoomFactor(self, lZoomFactor=None):
        """
        Will try to set the browser's zoom factor.

        :param lZoomFactor: set with a value. Otherwise existing value will be used (if previously set)
        :return:
        """

        isZoomed = False
        if self.zoomFactorDesired and lZoomFactor:
            self.zoomFactorDesired = int(lZoomFactor)
            if self.browserName == GC.BROWSER_CHROME:
                logger.critical(
                    f"Zoom in Chrome doesn't work. Continuing without zoom")
                return False
                x = self.getURL()
                if x[0:
                     5] == "http:":  # He loaded already something. Too late for us
                    logger.debug(
                        "CHROME: Got called to change Zoom level - but already URL loaded. Too late."
                    )
                else:
                    self.browserData.driver.get("chrome://settings/")
                    self.browserData.driver.execute_script(
                        f"chrome.settingsPrivate.setDefaultZoom({self.zoomFactorDesired/100});"
                    )
                    logger.debug(
                        f"CHROME: Set default zoom using JS-Method to {self.zoomFactorDesired/100}"
                    )
                    isZoomed = True
            elif self.browserName == GC.BROWSER_FIREFOX:
                self.browserData.driver.set_context("chrome")

                lZoomKey = "+" if self.zoomFactorDesired > 100 else "-"
                # E.g. current = 100. Desired = 67%: 100-67 = 33. 33/10 = 3.3  int(3.3) = 3 --> he'll hit 3 times CTRL+"-"
                lDifference = abs(100 - self.zoomFactorDesired)
                lHitKeyTimes = int(lDifference / 10)
                self._zoomFirefox(lZoomKey, lHitKeyTimes)
                isZoomed = True
            else:
                # statement not matched
                pass
        else:
            # statement not matched
            pass

        return isZoomed

    @staticmethod
    def downloadDriver(browserName):
        managedPaths = ManagedPaths()
        path = Path(managedPaths.getOrSetDriverPath())
        logger.debug(
            f"Trying to download browserDriver for {browserName} into {path}")
        path.mkdir(parents=True, exist_ok=True)
        if browserName == GC.BROWSER_FIREFOX:
            url, isTarFile = helper.browserHelper_getFirefoxFileUrl()
            if isTarFile:
                helper.browserHelper_extractTarDriverFile(
                    url, path, GC.GECKO_DRIVER)
            else:
                helper.browserHelper_unzipDriverFile(url, path,
                                                     GC.GECKO_DRIVER)
        elif browserName == GC.BROWSER_CHROME:
            url = helper.browserHelper_getChromeFileUrl()
            helper.browserHelper_unzipDriverFile(url, path, GC.CHROME_DRIVER)
        else:
            logger.critical(
                f"Please download driver for {browserName} manually into folder /browserDrivers"
            )
Ejemplo n.º 3
0
class TestRun:
    """
    This is the main Class of Testexecution in the baangt Framework. It is usually started
    from baangtIA.py
    """
    def __init__(self,
                 testRunName,
                 globalSettingsFileNameAndPath=None,
                 testRunDict=None,
                 uuid=uuid4(),
                 executeDirect=True,
                 noCloneXls=False):  # -- API support: testRunDict --
        """
        @param testRunName: The name of the TestRun to be executed.
        @param globalSettingsFileNameAndPath: from where to read the <globals>.json
        """

        # Take over importing parameters:
        self.uuid = uuid
        logger.info(f'Init Testrun, uuid is {self.uuid}')
        self.testRunDict = testRunDict
        self.globalSettingsFileNameAndPath = globalSettingsFileNameAndPath
        self.testRunName, self.testRunFileName = \
            self._sanitizeTestRunNameAndFileName(testRunName, executeDirect)

        # Initialize everything else
        self.apiInstance = None
        self.testType = None
        self.networkInfo = None
        self.results = None
        self.browserFactory = None
        self.kwargs = {}
        self.dataRecords = {}
        self.globalSettings = {}
        self.json_dict = {
        }  # Used to maintain records of RLP_ data which will be used will exporting results
        self.managedPaths = ManagedPaths()
        self.classesForObjects = ClassesForObjects(
        )  # Dynamically loaded classes
        self.timing = Timing()
        self.testRunUtils = TestRunUtils()
        self.testCasesEndDateTimes_1D = []  # refer to single execution
        self.testCasesEndDateTimes_2D = [[]]  # refer to parallel execution
        # New way to export additional Tabs to Excel
        # If you want to export additional data, place a Dict with Tabname + Datafields in additionalExportTabs
        # from anywhere within your custom code base.
        self.additionalExportTabs = {}
        self.statistics = Statistic()
        self.noCloneXls = noCloneXls
        signal.signal(signal.SIGINT, self.exit_signal_handler)
        signal.signal(signal.SIGTERM, self.exit_signal_handler)

        # Initialize other values
        self.timing.takeTime(GC.TIMING_TESTRUN)  # Initialize Testrun Duration

        # Usually the Testrun is called without the parameter executeDirect, meaning it default to "Execute"
        # during Unit-Tests we don't want this behaviour:
        if executeDirect:
            self.executeTestRun()

    def exit_signal_handler(self, signal, frame):
        self.browserFactory.teardown()

    def executeTestRun(self):
        self._initTestRunSettingsFromFile()  # Loads the globals*.json file

        self._loadJSONTestRunDefinitions()
        self._loadExcelTestRunDefinitions()

        self.browserFactory = BrowserFactory(self)
        self.executeTestSequence()
        self.tearDown()

        try:
            Sender.send_all(self.results, self.globalSettings)
        except Exception as ex:
            logger.error(f"Error from SendAll: {ex}")

    def append1DTestCaseEndDateTimes(self, dt):
        self.testCasesEndDateTimes_1D.append(dt)

    def append2DTestCaseEndDateTimes(self, index, tcAndDt):
        tc = tcAndDt[0]
        dt = tcAndDt[1]
        [
            self.testCasesEndDateTimes_2D.append([])
            for i in range(index + 1 - len(self.testCasesEndDateTimes_2D))
        ] if index + 1 > len(self.testCasesEndDateTimes_2D) else None
        self.testCasesEndDateTimes_2D[index].append([tc, dt])

    def tearDown(self):
        """
        Close browser (unless stated in the Globals to not do so) and API-Instances
        Take overall Time spent for the complete TestRun
        Write results of TestRun to output channel(s)
        """

        self.timing.takeTime(GC.TIMING_TESTRUN)
        self.timing.takeTimeSumOutput()

        if self.apiInstance:
            self.apiInstance.tearDown()

        network_info = self.browserFactory.teardown()
        self.kwargs['networkInfo'] = network_info

        if self.testCasesEndDateTimes_1D:
            self.kwargs[
                'testCasesEndDateTimes_1D'] = self.testCasesEndDateTimes_1D

        if self.testCasesEndDateTimes_2D and self.testCasesEndDateTimes_2D[0]:
            self.kwargs[
                'testCasesEndDateTimes_2D'] = self.testCasesEndDateTimes_2D

        if len(self.additionalExportTabs) > 0:
            self.kwargs[GC.EXPORT_ADDITIONAL_DATA] = self.additionalExportTabs

        self.results = ExportResults(
            **self.kwargs)  # -- API support: self.results --
        successful, error = self.getSuccessAndError()
        waiting = self.getWaiting()
        self.statistics.update_all(successful, error, waiting)
        logger.info(
            f"Finished execution of Testrun {self.testRunName}. "
            f"{successful} Testcases successfully executed, {error} errors")
        print(f"Finished execution of Testrun {self.testRunName}. "
              f"{successful} Testcases successfully executed, {error} errors")

    def getSuccessAndError(self):
        """
        Returns number of successful and number of error test cases of the current test run
        @rtype: object
        """
        lError = 0
        lSuccess = 0
        for value in self.dataRecords.values():
            if value[GC.TESTCASESTATUS] == GC.TESTCASESTATUS_ERROR:
                lError += 1
            elif value[GC.TESTCASESTATUS] == GC.TESTCASESTATUS_SUCCESS:
                lSuccess += 1
        return lSuccess, lError

    def getWaiting(self):
        lWaiting = 0
        for value in self.dataRecords.values():
            if value[GC.TESTCASESTATUS] == GC.TESTCASESTATUS_WAITING:
                lWaiting += 1
        return lWaiting

    def getAllTestRunAttributes(self):
        return self.testRunUtils.getCompleteTestRunAttributes(self.testRunName)

    def getBrowser(self,
                   browserInstance=0,
                   browserName=None,
                   browserAttributes=None,
                   mobileType=None,
                   mobileApp=None,
                   desired_app=None,
                   mobile_app_setting=None,
                   browserWindowSize=None):

        return self.browserFactory.getBrowser(
            browserInstance=browserInstance,
            browserName=browserName,
            browserAttributes=browserAttributes,
            mobileType=mobileType,
            mobileApp=mobileApp,
            desired_app=desired_app,
            mobile_app_setting=mobile_app_setting,
            browserWindowSize=browserWindowSize)

    def getAPI(self):
        if not self.apiInstance:
            self.apiInstance = ApiHandling()
        return self.apiInstance

    def setResult(self, recordNumber, dataRecordResult):
        logger.debug(f"Received new result for Testrecord {recordNumber}")
        self.dataRecords[recordNumber] = dataRecordResult

    def executeTestSequence(self):
        """
        Start TestcaseSequence

        TestCaseSequence is a sequence of Testcases. In the TestcaseSequence there's a sequential List of
        Testcases to be executed.

        Before the loop (executeDictSequenceOfClasses) variables inside the the testrun-definition are replaced
        by values from the globals-file (e.g. if you want to generally run with FF, but in a certain case you want to
        run with Chrome, you'd have FF in the Testrundefinition, but set parameter in globals_chrome.json accordingly
        (in this case {"TC.Browser": "CHROME"}. TC.-Prefix signals the logic to look for this variable ("Browser")
        inside the testcase definitions and replace it with value "CHROME".

        """
        self.testRunUtils.replaceGlobals(self.globalSettings)
        self.testRunUtils.replaceClasses(self.testRunName,
                                         self.classesForObjects)

        kwargs = {
            GC.KWARGS_TESTRUNATTRIBUTES: self.getAllTestRunAttributes(),
            GC.KWARGS_TESTRUNINSTANCE: self,
            GC.KWARGS_TIMING: self.timing
        }

        self.executeDictSequenceOfClasses(
            kwargs[GC.KWARGS_TESTRUNATTRIBUTES][GC.STRUCTURE_TESTCASESEQUENCE],
            counterName=GC.STRUCTURE_TESTCASESEQUENCE,
            **kwargs)

    def executeDictSequenceOfClasses(self, dictSequenceOfClasses, counterName,
                                     **kwargs):
        """
        This is the main loop of the TestCaseSequence, TestCases, TestStepSequences and TestSteps.
        The Sequence of which class instance to create is defined by the TestRunAttributes.

        Before instancgetBrowsering the class it is checked, whether the class was loaded already and if not, will be loaded
        (only if the classname is fully qualified (e.g baangt<projectname>.TestSteps.myTestStep).
        If the testcase-Status is already "error" (GC.TESTCASESTATUS_ERROR) we'll stop the loop.

        @param dictSequenceOfClasses: The list of classes to be instanced. Must be a dict of {Enum, Classname},
        can be also {enum: [classname, <whatEverElse>]}
        @param counterName: Which Structure element we're currently looping, e.g. "TestStep" (GC.STRUCTURE_TESTSTEP)
        @param kwargs: TestrunAttributes, this TestRun, the Timings-Instance, the datarecord

        """
        if not kwargs.get(GC.KWARGS_TESTRUNATTRIBUTES):
            kwargs[
                GC.KWARGS_TESTRUNATTRIBUTES] = self.getAllTestRunAttributes()
        if not kwargs.get(GC.KWARGS_TESTRUNINSTANCE):
            kwargs[GC.KWARGS_TESTRUNINSTANCE] = self
            logger.info(
                'get into not kwargs.getGC.KWARGS_TESTRUNINSTANCE, id is {}'.
                format(id(self)))
        if not kwargs.get(GC.KWARGS_TIMING):
            kwargs[GC.KWARGS_TIMING] = self.timing
        for key, value in dictSequenceOfClasses.items():
            # If any of the previous steps set the testcase to "Error" - exit here.
            if kwargs.get(GC.KWARGS_DATA):
                if kwargs[GC.KWARGS_DATA][
                        GC.TESTCASESTATUS] == GC.TESTCASESTATUS_ERROR:
                    logger.info(
                        f"TC is already in status Error - not processing steps {counterName}: {key}, {value}"
                        f"and everything behind this step")
                    return
                if kwargs[GC.KWARGS_DATA].get(GC.TESTCASESTATUS_STOP):
                    logger.info(
                        f"TC wanted to stop. Not processing steps {counterName}: {key}, {value}"
                        f"and everything behind this step. TC-Status is "
                        f"{kwargs[GC.KWARGS_DATA][GC.TESTCASESTATUS]}")
                    return
                if kwargs[GC.KWARGS_DATA].get(GC.TESTCASESTATUS_STOPERROR):
                    kwargs[GC.KWARGS_DATA][
                        GC.TESTCASESTATUS] = GC.TESTCASESTATUS_ERROR
                    if not kwargs[GC.KWARGS_DATA].get(GC.TESTCASEERRORLOG):
                        kwargs[GC.KWARGS_DATA][
                            GC.
                            TESTCASEERRORLOG] = "Aborted by command 'TCStopTestCaseError'"
                    else:
                        kwargs[GC.KWARGS_DATA][GC.TESTCASEERRORLOG] = kwargs[GC.KWARGS_DATA][GC.TESTCASEERRORLOG] \
                                                                      + "\nAborted by command 'TCStopTestCaseError'"
                    return

            logger.info(f"Starting {counterName}: {key}")
            kwargs[counterName] = key

            # Get the class reference:
            if isinstance(value, list):
                lFullQualified = value[
                    0]  # First List-Entry must hold the ClassName
            else:
                lFullQualified = value

            l_class = TestRun.__dynamicImportClasses(lFullQualified)

            try:
                l_class(**kwargs)  # Executes the class´es  __init__ method
            except TypeError as e:
                # Damn! The class is wrong.
                l_class = TestRun.__dynamicImportClasses(lFullQualified)
                l_class(**kwargs)

        self.kwargs = kwargs

    def _initTestRunSettingsFromFile(self):
        self.__loadJSONGlobals()
        self.__setPathsIfNotPredefined()
        self.__sanitizeGlobalsValues()

    def __setPathsIfNotPredefined(self):
        if not self.globalSettings.get(GC.PATH_SCREENSHOTS, None):
            self.globalSettings[GC.PATH_SCREENSHOTS] = str(
                Path(self.managedPaths.getOrSetScreenshotsPath()).expanduser())
        else:
            self.managedPaths.getOrSetScreenshotsPath(
                path=self.globalSettings.get(GC.PATH_SCREENSHOTS))
        if not self.globalSettings.get(GC.PATH_EXPORT, None):
            self.globalSettings[GC.PATH_EXPORT] = str(
                Path(self.managedPaths.getOrSetExportPath()).expanduser())
        else:
            self.managedPaths.getOrSetExportPath(
                path=self.globalSettings.get(GC.PATH_EXPORT))
        if not self.globalSettings.get(GC.PATH_IMPORT, None):
            self.globalSettings[GC.PATH_IMPORT] = str(
                Path(self.managedPaths.getOrSetImportPath()).expanduser())
        else:
            self.managedPaths.getOrSetImportPath(
                path=self.globalSettings.get(GC.PATH_IMPORT))
        if not self.globalSettings.get(GC.PATH_ROOT, None):
            self.globalSettings[GC.PATH_ROOT] = str(
                Path(self.managedPaths.getOrSetRootPath()).parent.expanduser())
        else:
            self.managedPaths.getOrSetRootPath(
                path=self.globalSettings.get(GC.PATH_ROOT))

    def __loadJSONGlobals(self):
        if self.globalSettingsFileNameAndPath:
            self.globalSettings = utils.openJson(
                self.globalSettingsFileNameAndPath)

        # Set default execution STAGE
        if not self.globalSettings.get(GC.EXECUTION_STAGE, None):
            logger.debug(
                f"Execution Stage was not set. Setting to default value {GC.EXECUTION_STAGE_TEST}"
            )
            self.globalSettings[GC.EXECUTION_STAGE] = GC.EXECUTION_STAGE_TEST

    def __sanitizeGlobalsValues(self):
        # Support for new dataClass to load different Classes
        for key, value in self.globalSettings.items():
            if "CL." in key:
                self.classesForObjects.__setattr__(key.strip("CL."), value)

            # Change boolean strings into boolean values.
            if isinstance(value, str):
                if value.lower() in ("false", "true", "no", "x"):
                    self.globalSettings[key] = utils.anything2Boolean(value)
                elif "renv_" in value.lower():
                    self.globalSettings[
                        key] = TestDataGenerator.get_env_variable(value[5:])
            if isinstance(value, dict):
                if "default" in value:
                    # This happens in the new UI, if a value was added manually,
                    # but is not part of the globalSetting.json. In this case there's the whole shebang in a dict. We
                    # are only interested in the actual value, which is stored in "default":
                    self.globalSettings[key] = value["default"]
                    if isinstance(self.globalSettings[key], str):
                        if "renv_" in self.globalSettings[key].lower():
                            self.globalSettings[
                                key] = TestDataGenerator.get_env_variable(
                                    self.globalSettings[key][5:])
                    continue
                else:
                    # This could be the "old" way of the globals-file (with {"HEADLESS":"True"})
                    self.globalSettings[key] = value
                    continue

            if isinstance(value, str) and len(value) > 0:
                if value[0] == "{" and value[-1] == "}":
                    # Dict, that is not seen as dict
                    value = value.replace("\'", '"')
                    self.globalSettings[key] = json.loads(value)

        if self.globalSettings.get("TC." + GC.EXECUTION_LOGLEVEL):
            utils.setLogLevel(
                self.globalSettings.get("TC." + GC.EXECUTION_LOGLEVEL))

    def _loadJSONTestRunDefinitions(self):
        if not self.testRunFileName and not self.testRunDict:  # -- API support: testRunDict --
            return

        if self.testRunFileName and ".JSON" in self.testRunFileName.upper(
        ):  # -- API support: self.testRunFileName --
            data = utils.replaceAllGlobalConstantsInDict(
                utils.openJson(self.testRunFileName))
            self.testRunUtils.setCompleteTestRunAttributes(
                testRunName=self.testRunName, testRunAttributes=data)

        # -- API support --
        # load TestRun from dict
        if self.testRunDict:
            data = utils.replaceAllGlobalConstantsInDict(self.testRunDict)
            self.testRunUtils.setCompleteTestRunAttributes(
                testRunName=self.testRunName, testRunAttributes=data)
        # -- END of API support --

    def _loadExcelTestRunDefinitions(self):
        if not self.testRunFileName:
            return

        if ".XLSX" in self.testRunFileName.upper():
            logger.info(f"Reading Definition from {self.testRunFileName}")
            lExcelImport = TestRunExcelImporter(
                FileNameAndPath=self.testRunFileName,
                testRunUtils=self.testRunUtils)
            lExcelImport.importConfig(global_settings=self.globalSettings)

    @staticmethod
    def __dynamicImportClasses(fullQualifiedImportName):
        return utils.dynamicImportOfClasses(
            fullQualifiedImportName=fullQualifiedImportName)

    @staticmethod
    def _sanitizeTestRunNameAndFileName(TestRunNameInput, direct):
        """
        @param TestRunNameInput: The complete File and Path of the TestRun definition (JSON or XLSX).
        @return: TestRunName and FileName (if definition of testrun comes from a file (JSON or XLSX)
        """
        if ".XLSX" in TestRunNameInput.upper():
            cloneXls = CloneXls(TestRunNameInput)
            lFileName = cloneXls.update_or_make_clone(
                ignore_headers=["TestResult", "UseCount"], clone=direct)
            lRunName = utils.extractFileNameFromFullPath(lFileName)
        elif ".JSON" in TestRunNameInput.upper():
            lRunName = utils.extractFileNameFromFullPath(TestRunNameInput)
            lFileName = TestRunNameInput
        else:
            lRunName = TestRunNameInput
            lFileName = None

        return lRunName, lFileName