Browse Source

Merge branch 'master' into results_browser

aguryev 3 years ago
parent
commit
1e68391c19

+ 1 - 1
.bumpversion.cfg

@@ -1,5 +1,5 @@
 [bumpversion]
-current_version = 1.1.20
+current_version = 1.2.3
 commit = True
 tag = True
 

+ 8 - 1
baangt/TestCase/TestCaseMaster.py

@@ -2,6 +2,7 @@ from baangt.base import GlobalConstants as GC
 from baangt.base.Timing.Timing import Timing
 from baangt.TestSteps.Exceptions import *
 from baangt.base.RuntimeStatistics import Statistic
+from baangt.base.Utils import utils
 
 
 class TestCaseMaster:
@@ -31,7 +32,13 @@ class TestCaseMaster:
         # In Unit-Tests this is a problem. When we run within the main loop of TestRun we are expected to directly
         # execute on __init__.
         if executeDirect:
-            self.executeTestCase()
+            try:
+                self.executeTestCase()
+            except Exception as e:
+                logger.warning(f"Uncought exception {e}")
+                utils.traceback(exception_in=e)
+                self.kwargs[GC.KWARGS_DATA][GC.TESTCASESTATUS_STOPERROR] = True
+                self.kwargs[GC.KWARGS_DATA][GC.TESTCASESTATUS] = GC.TESTCASESTATUS_ERROR
 
     def executeTestCase(self):
         self.timingName = self.timing.takeTime(self.__class__.__name__, forceNew=True)

+ 27 - 18
baangt/TestCaseSequence/TestCaseSequenceMaster.py

@@ -13,6 +13,7 @@ import gevent
 import gevent.queue
 import gevent.pool
 from baangt.base.RuntimeStatistics import Statistic
+from CloneXls import CloneXls
 
 logger = logging.getLogger("pyC")
 
@@ -33,14 +34,20 @@ class TestCaseSequenceMaster:
         self.testCases = self.testSequenceData[GC.STRUCTURE_TESTCASE]
         self.kwargs = kwargs
         self.timingName = self.timing.takeTime(self.__class__.__name__, forceNew=True)
-        self.statistics = Statistic()
-        self.prepareExecution()
-        if int(self.testSequenceData.get(GC.EXECUTION_PARALLEL, 0)) > 1:
-            self.execute_parallel(self.testSequenceData.get(GC.EXECUTION_PARALLEL, 0))
-        else:
-            self.execute()
+
+        try:
+            self.statistics = Statistic()
+            self.prepareExecution()
+            if int(self.testSequenceData.get(GC.EXECUTION_PARALLEL, 0)) > 1:
+                self.execute_parallel(self.testSequenceData.get(GC.EXECUTION_PARALLEL, 0))
+            else:
+                self.execute()
+        except Exception as e:
+            logger.warning(f"Uncought exception {e}")
+            utils.traceback(exception_in=e)
 
     def prepareExecution(self):
+        logger.info("Preparing Test Records...")
         if self.testSequenceData.get(GC.DATABASE_FROM_LINE) and not self.testSequenceData.get(GC.DATABASE_LINES, None):
             # Change old line selection format into new format:
             self.testSequenceData[
@@ -57,14 +64,10 @@ class TestCaseSequenceMaster:
                 recordPointer -= 1
                 break
             recordPointer += 1
-        try:
-            self.testdataDataBase.update_datarecords(self.dataRecords, fileName=utils.findFileAndPathFromPath(
-                self.testSequenceData[GC.DATABASE_FILENAME],
-                basePath=str(Path(self.testRunInstance.globalSettingsFileNameAndPath).parent)))
-        except Exception as e:
-            logger.error(f"Error during update_datarecords: {e}, Terminating.")
-            import sys
-            sys.exit(f"Error during update_datarecords: {e}")
+        self.testdataDataBase.update_datarecords(self.dataRecords, fileName=utils.findFileAndPathFromPath(
+            self.testSequenceData[GC.DATABASE_FILENAME],
+            basePath=str(Path(self.testRunInstance.globalSettingsFileNameAndPath).parent)),
+            sheetName=self.testSequenceData[GC.DATABASE_SHEETNAME], noCloneXls=self.testRunInstance.noCloneXls)
         logger.info(f"{recordPointer + 1} test records read for processing")
         self.statistics.total_testcases(recordPointer + 1)
 
@@ -142,10 +145,16 @@ class TestCaseSequenceMaster:
         if not self.testdataDataBase:
             self.testdataDataBase = HandleDatabase(globalSettings=self.testRunInstance.globalSettings,
                                                    linesToRead=self.testSequenceData.get(GC.DATABASE_LINES))
-            self.testdataDataBase.read_excel(
-                fileName=utils.findFileAndPathFromPath(
-                    self.testSequenceData[GC.DATABASE_FILENAME],
-                    basePath=str(Path(self.testRunInstance.globalSettingsFileNameAndPath).parent)),
+        testDataFile = utils.findFileAndPathFromPath(
+            self.testSequenceData[GC.DATABASE_FILENAME],
+            basePath=str(Path(self.testRunInstance.globalSettingsFileNameAndPath).parent))
+        if not self.testRunInstance.noCloneXls:
+            cloneXls = CloneXls(testDataFile)  # , logger=logger)
+            testDataFile = cloneXls.update_or_make_clone(
+                ignore_headers=["TestResult", "UseCount"])
+        self.testSequenceData[GC.DATABASE_FILENAME] = testDataFile
+        self.testdataDataBase.read_excel(
+                fileName=testDataFile,
                 sheetName=self.testSequenceData[GC.DATABASE_SHEETNAME])
         return self.testdataDataBase
 

+ 2 - 0
baangt/TestCaseSequence/TestCaseSequenceParallel.py

@@ -1,6 +1,7 @@
 import logging
 from baangt.TestSteps import Exceptions
 from baangt.base import GlobalConstants as GC
+from baangt.base.Utils import utils
 from datetime import datetime
 import time
 import gevent.queue
@@ -38,6 +39,7 @@ class TestCaseSequenceParallel:
 
         except Exceptions.baangtTestStepException as e:
             logger.critical(f"Unhandled Error happened in parallel run {parallelizationSequenceNumber}: " + str(e))
+            utils.traceback(exception_in=e)
             dataRecord[GC.TESTCASESTATUS] = GC.TESTCASESTATUS_ERROR
         finally:
             d_t = datetime.fromtimestamp(time.time())

+ 165 - 102
baangt/TestDataGenerator/TestDataGenerator.py

@@ -1,8 +1,6 @@
 import csv
 import itertools
 import xlsxwriter
-import xl2dict
-import xlrd3 as xlrd
 import errno
 import os
 import logging
@@ -11,6 +9,10 @@ from random import sample, randint
 import baangt.base.GlobalConstants as GC
 import re
 from openpyxl import load_workbook
+import sys
+import pandas as pd
+from CloneXls import CloneXls
+import json
 
 logger = logging.getLogger("pyC")
 
@@ -24,7 +26,7 @@ class Writer:
     """
     def __init__(self, path):
         self.path = path
-        self.workbook = load_workbook(self.path)
+        self.workbook = load_workbook(path)
 
     def write(self, row, data, sht):
         # Update the values using row and col number.
@@ -38,7 +40,6 @@ class Writer:
         if column:
             sheet.cell(row, column).value = data
 
-
     def save(self):
         # Call this method to save the file once every updates are written
         self.workbook.save(self.path)
@@ -62,20 +63,28 @@ class TestDataGenerator:
     :param sheetName: Name of sheet where all base data is located.
     :method write: Will write the final processed data in excel/csv file.
     """
-    def __init__(self, rawExcelPath=GC.TESTDATAGENERATOR_INPUTFILE, sheetName=""):
+    def __init__(self, rawExcelPath=GC.TESTDATAGENERATOR_INPUTFILE, sheetName="",
+                 from_handleDatabase=False, noUpdate=False):
         self.path = os.path.abspath(rawExcelPath)
         self.sheet_name = sheetName
         if not os.path.isfile(self.path):
             raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), self.path)
-        self.sheet_dict, self.raw_data_json = self.__read_excel(self.path, self.sheet_name)
+        self.sheet_dict, self.raw_data_json = self.read_excel(self.path, self.sheet_name)
+        self.rre_sheets = {}
+        self.isUsecount = {}
         self.remove_header = []
         self.usecount_dict = {}  # used to maintain usecount limit record and verify if that non of the data cross limit
-        self.processed_datas = self.__process_data(self.raw_data_json)
-        self.headers = [x for x in list(self.processed_datas[0].keys()) if x not in self.remove_header]
-        self.headers = [x for x in self.headers if 'usecount' not in x.lower()]
-        self.writer = Writer(self.path)  # Writer class object to save file only in end, which will save time
-        self.final_data = self.__generateFinalData(self.processed_datas)
-        self.writer.save()  # saving source input file once everything is done
+        self.done = {}
+        self.noUpdateFiles = noUpdate
+        self.writers = {}
+        if not from_handleDatabase:
+            self.processed_datas = self.__process_data(self.raw_data_json)
+            self.headers = [x for x in list(self.processed_datas[0].keys()) if x not in self.remove_header]
+            self.headers = [x for x in self.headers if 'usecount' not in x.lower()]
+            self.final_data = self.__generateFinalData(self.processed_datas)
+            if self.isUsecount:
+                if not self.noUpdateFiles:
+                    self.save_usecount()  # saving source input file once everything is done
 
     def write(self, OutputFormat=GC.TESTDATAGENERATOR_OUTPUT_FORMAT, batch_size=0, outputfile=None):
         """
@@ -353,14 +362,15 @@ class TestDataGenerator:
         :return:
         """
         processed_datas = []
+        raw_json = json.loads(raw_json.to_json(orient="records"))
         for raw_data in raw_json:
             if not list(raw_data.values())[0]:
                 continue
             processed_data = {}
             for key in raw_data:
-                keys = self.__data_generators(key)
+                keys = self.data_generators(key)
                 for ke in keys:
-                    processed_data[ke] = self.__data_generators(raw_data[key])
+                    processed_data[ke] = self.data_generators(raw_data[key])
                     if type(processed_data[ke]) == tuple and len(processed_data[ke])>0:
                         if type(processed_data[ke][0]) == dict:
                             if ke not in processed_data[ke][0]:
@@ -368,7 +378,7 @@ class TestDataGenerator:
             processed_datas.append(processed_data)
         return processed_datas
 
-    def __data_generators(self, raw_data):
+    def data_generators(self, raw_data_old):
         """
         This method first send the data to ``__raw_data_string_process`` method to split the data and remove the unwanted
         spaces.
@@ -387,7 +397,7 @@ class TestDataGenerator:
         :param raw_data:
         :return: List or Tuple containing necessary data
         """
-        raw_data, prefix, data_type = self.__raw_data_string_process(raw_data)
+        raw_data, prefix, data_type = self.__raw_data_string_process(raw_data_old)
         if len(raw_data)<=1:
             return [""]
         if raw_data[0] == "[" and raw_data[-1] == "]" and prefix == "":
@@ -417,12 +427,14 @@ class TestDataGenerator:
                 }
             if second_value[0] == "[" and second_value[-1] == "]":
                 second_value = self.__splitList(second_value)
-            processed_datas = self.__processRrd(first_value, second_value,evaluated_dict)
-            processed_datas = data_type(processed_datas)
+            try:
+                processed_datas = self.__processRrdRre(first_value, second_value,evaluated_dict)
+                processed_datas = data_type(processed_datas)
+            except KeyError:
+                sys.exit(f"Please check that source files contains all the headers mentioned in : {raw_data_old}")
 
         elif prefix == "Rre":
             file_name = raw_data[1:-1].split(',')[0].strip()
-            sheet_dict, _ = self.__read_excel(file_name)
             first_value = raw_data[1:-1].split(',')[1].strip()
             second_value = raw_data[1:-1].split(',')[2].strip()
             if second_value[0] == "[":
@@ -440,7 +452,11 @@ class TestDataGenerator:
                 }
             if second_value[0] == "[" and second_value[-1] == "]":
                 second_value = self.__splitList(second_value)
-            processed_datas = self.__processRrd(first_value, second_value, evaluated_dict, sheet_dict)
+            try:
+                processed_datas = self.__processRrdRre(first_value, second_value, evaluated_dict, filename=file_name)
+            except KeyError as e:
+                raise e
+                sys.exit(f"Please check that source files contains all the headers mentioned in : {raw_data_old}")
             processed_datas = data_type(processed_datas)
 
         elif prefix == "Renv":
@@ -463,6 +479,87 @@ class TestDataGenerator:
             processed_datas = data_type(processed_datas)
         return processed_datas
 
+    def __processRrdRre(self, sheet_name, data_looking_for, data_to_match: dict, filename=None):
+        if filename:
+            file_name = ".".join(filename.split(".")[:-1])
+            file_extension = filename.split(".")[-1]
+            file = file_name + "_baangt" + "." + file_extension
+            if not file in self.rre_sheets:
+                logger.debug(f"Creating clone file of: {filename}")
+                if not self.noUpdateFiles:
+                    filename = CloneXls(filename).update_or_make_clone()
+                    self.rre_sheets[filename] = {}
+            filename = file
+            if sheet_name in self.rre_sheets[filename]:
+                df = self.rre_sheets[filename][sheet_name]
+            else:
+                df = pd.read_excel(filename, sheet_name, dtype=str)
+                self.rre_sheets[filename][sheet_name] = df
+        else:
+            df = self.sheet_dict[sheet_name]
+            if not self.path in self.rre_sheets:
+                self.rre_sheets[self.path] = {}
+            if not sheet_name in self.rre_sheets[self.path]:
+                self.rre_sheets[self.path][sheet_name] = df
+        df1 = df.copy()
+        for key, value in data_to_match.items():
+            if not isinstance(value, list):
+                value = [value]
+            df1 = df1.loc[df1[key].isin(value)]
+        data_lis = []
+
+        if type(data_looking_for) == str:
+            data_looking_for = data_looking_for.split(",")
+        key_name = repr(sheet_name) + repr(data_looking_for) + repr(data_to_match) + repr(filename)
+        if key_name in self.done:
+            logger.debug(f"Data Gathered from previously saved data.")
+            return self.done[key_name]
+
+        usecount, limit, usecount_header = self.check_usecount(df.columns.values.tolist())
+        if not filename:
+            if self.path not in self.isUsecount:
+                self.isUsecount[self.path] = usecount_header
+            if not self.isUsecount[self.path]:
+                self.isUsecount[self.path] = usecount_header
+        else:
+            if filename not in self.isUsecount:
+                self.isUsecount[filename] = usecount_header
+            if not self.isUsecount[filename]:
+                self.isUsecount[filename] = usecount_header
+        for tup in df1.itertuples():
+            data = dict(tup._asdict())
+            if usecount_header:
+                try:
+                    used_limit = int(data[usecount_header])
+                except:
+                    used_limit = 0
+            else:
+                used_limit = 0
+
+            if data_looking_for[0] == "*":
+                index = data["Index"]
+                del data["Index"]
+                data_lis.append(data)
+                self.usecount_dict[repr(data)] = {
+                    "use": used_limit, "limit": limit, "index": index,
+                    "sheet_name": sheet_name, "file_name": filename
+                }
+            else:
+                dt = {keys: data[keys] for keys in data_looking_for}
+                data_lis.append(dt)
+                self.usecount_dict[repr(dt)] = {
+                    "use": used_limit, "limit": limit, "index": data["Index"],
+                    "sheet_name": sheet_name, "file_name": filename
+                }
+
+        if len(data_lis) == 0:
+            logger.info(f"No data matching: {data_to_match}")
+            sys.exit(f"No data matching: {data_to_match}")
+        logger.debug(f"New Data Gathered.")
+        self.done[key_name] = data_lis
+
+        return data_lis
+
     def __raw_data_string_process(self, raw_string):
         """
         Returns ``String, prefix, data_type`` which are later used to decided the process to perform on string.
@@ -516,8 +613,13 @@ class TestDataGenerator:
             data_type = list
         return raw_string, prefix, data_type
 
-    @staticmethod
-    def __read_excel(path, sheet_name=""):
+    def get_str_sheet(self, excel, sheet):
+        columns = excel.parse(sheet).columns
+        converters = {column: str for column in columns}
+        data = excel.parse(sheet, converters=converters)
+        return data
+
+    def read_excel(self, path, sheet_name="", return_json=False):
         """
         This method will read the input excel file.
         It will read all the sheets inside this excel file and will create a dictionary of dictionary containing all data
@@ -533,19 +635,21 @@ class TestDataGenerator:
         :param sheet_name: Name of base sheet sheet where main input data is located. Default will be the first sheet.
         :return: Dictionary of all sheets and data, Dictionary of base sheet.
         """
-        wb = xlrd.open_workbook(path)
-        sheet_lis = wb.sheet_names()
-        sheet_dict = {}
+        wb = pd.ExcelFile(path)
+        sheet_lis = wb.sheet_names
+        sheet_df = {}
         for sheet in sheet_lis:
-            xl_obj = xl2dict.XlToDict()
-            data = xl_obj.fetch_data_by_column_by_sheet_name(path,sheet_name=sheet)
-            sheet_dict[sheet] = data
+            sheet_df[sheet] = self.get_str_sheet(wb, sheet)
+            sheet_df[sheet].fillna("", inplace=True)
+        if return_json:
+            for df in sheet_df.keys():
+                sheet_df[df] = json.loads(sheet_df[df].to_json(orient="records"))
         if sheet_name == "":
-            base_sheet = sheet_dict[sheet_lis[0]]
+            base_sheet = sheet_df[sheet_lis[0]]
         else:
-            assert sheet_name in sheet_dict, f"Excel file doesn't contain {sheet_name} sheet. Please recheck."
-            base_sheet = sheet_dict[sheet_name]
-        return sheet_dict, base_sheet
+            assert sheet_name in sheet_df, f"Excel file doesn't contain {sheet_name} sheet. Please recheck."
+            base_sheet = sheet_df[sheet_name]
+        return sheet_df, base_sheet
 
     @staticmethod
     def __splitList(raw_data):
@@ -558,72 +662,6 @@ class TestDataGenerator:
         proccesed_datas = [data.strip() for data in raw_data[1:-1].split(",")]
         return proccesed_datas
 
-    def __processRrd(self, sheet_name, data_looking_for, data_to_match: dict, sheet_dict=None, caller="RRD_"):
-        """
-        This function is internal function to process the data wil RRD_ prefix.
-        The General input in excel file is like ``RRD_[sheetName,TargetData,[Header:[values**],Header:[values**]]]``
-        So this program will take already have processed input i.e. strings converted as python string and list to
-        python list, vice versa.
-
-        ``sheet_name`` is the python string referring to TargetData containing string.
-
-        ``data_looking_for`` is expected to be a string or list of TargetData. When there are multiple values then the
-        previous process will send list else it will send string. When a string is received it will be automatically
-        converted in list so the program will alwaus have to deal with list. If input is "*" then this method will take
-        all value in the matched row as TargetData.
-
-        ``data_to_match`` is a python dictionary created by the previous process. It will contain the key value pair
-        same as given in input but just converted in python dict. Then all possible combinations will be generated inside
-        this method. If an empty list is given by the user in the excel file then this list will get emptu dictionary from
-        the previous process. Thus then this method will pick TargetData from all rows of the target sheet.
-
-        :param sheet_name:
-        :param data_looking_for:
-        :param data_to_match:
-        :return: dictionary of TargetData
-        """
-        sheet_dict = self.sheet_dict if sheet_dict is None else sheet_dict
-        matching_data = [list(x) for x in itertools.product(*[data_to_match[key] for key in data_to_match])]
-        assert sheet_name in sheet_dict, \
-            f"Excel file doesn't contain {sheet_name} sheet. Please recheck. Called in '{caller}'"
-        base_sheet = sheet_dict[sheet_name]
-        data_lis = []
-        if type(data_looking_for) == str:
-            data_looking_for = data_looking_for.split(",")
-
-        usecount, limit, usecount_header = self.check_usecount(base_sheet[0])
-        for data in base_sheet:
-            if usecount_header:
-                used_limit = data[usecount_header]
-            else:
-                used_limit = 0
-            if len(matching_data) == 1 and len(matching_data[0]) == 0:
-                if data_looking_for[0] == "*":
-                    data_lis.append(data)
-                    self.usecount_dict[repr(data)] = {
-                        "use": used_limit, "limit": limit, "index": base_sheet.index(data) + 2, "sheet_name": sheet_name
-                    }
-                else:
-                    dt = {keys: data[keys] for keys in data_looking_for}
-                    data_lis.append(dt)
-                    self.usecount_dict[repr(dt)] = {
-                        "use" : used_limit, "limit" : limit, "index": base_sheet.index(data) + 2, "sheet_name": sheet_name
-                    }
-            else:
-                if [data[key] for key in data_to_match] in matching_data:
-                    if data_looking_for[0] == "*":
-                        data_lis.append(data)
-                        self.usecount_dict[repr(data)] = {
-                        "use": used_limit, "limit": limit, "index": base_sheet.index(data) + 2, "sheet_name": sheet_name
-                    }
-                    else:
-                        dt = {keys: data[keys] for keys in data_looking_for}
-                        data_lis.append(dt)
-                        self.usecount_dict[repr(dt)] = {
-                        "use" : used_limit, "limit" : limit, "index": base_sheet.index(data) + 2, "sheet_name": sheet_name
-                    }
-        return data_lis
-
     def check_usecount(self, data):
         # used to find and return if their is usecount header and limit in input file
         usecount = False
@@ -640,11 +678,36 @@ class TestDataGenerator:
                         limit = 0
         return usecount, limit, usecount_header
 
+    def save_usecount(self):
+        if self.noUpdateFiles:
+            return 
+        for filename in self.isUsecount:
+            logger.debug(f"Updating file {filename} with usecounts.")
+            sheet_dict = self.rre_sheets[filename]
+            ex = pd.ExcelFile(filename)
+            for sheet in ex.sheet_names:
+                if sheet in sheet_dict:
+                    continue
+                df = self.get_str_sheet(ex, sheet)
+                sheet_dict[sheet] = df
+            with pd.ExcelWriter(filename) as writer:
+                for sheetname in sheet_dict:
+                    sheet_dict[sheetname].to_excel(writer, sheetname, index=False)
+                writer.save()
+            logger.debug(f"File updated {filename}.")
+
     def update_usecount_in_source(self, data):
-        self.writer.write(
-            self.usecount_dict[repr(data)]["index"], self.usecount_dict[repr(data)]["use"],
-            self.usecount_dict[repr(data)]["sheet_name"]
-        )
+        if self.noUpdateFiles:
+            return 
+        filename = self.usecount_dict[repr(data)]["file_name"]
+        if not filename:
+            filename = self.path
+        if filename not in self.isUsecount:
+            return
+        if not self.isUsecount[filename]:
+            return
+        self.rre_sheets[filename][self.usecount_dict[repr(data)]["sheet_name"]][
+            self.isUsecount[filename]][self.usecount_dict[repr(data)]["index"]] = self.usecount_dict[repr(data)]["use"]
 
     def __process_rrd_string(self, rrd_string):
         """

+ 45 - 17
baangt/TestSteps/TestStepMaster.py

@@ -57,17 +57,21 @@ class TestStepMaster:
         self.testCase = self.testRunUtil.getTestCaseByNumber(lSequence, kwargs.get(GC.STRUCTURE_TESTCASE))
         self.testStep = self.testRunUtil.getTestStepByNumber(self.testCase, self.testStepNumber)
 
-        if self.testStep and len(self.testStep) > 1:
-            if not isinstance(self.testStep[1], str) and executeDirect:
-                # This TestStepMaster-Instance should actually do something - activitites are described
-                # in the TestExecutionSteps.
-                # Otherwise there's only a classname in TestStep[0]
-                self.executeDirect(self.testStep[1][GC.STRUCTURE_TESTSTEPEXECUTION])
-
-                # Teardown makes only sense, when we actually executed something directly in here
-                # Otherwise (if it was 1 or 2 Tab-stops more to the left) we'd take execution time without
-                # having done anything
-                self.teardown()
+        try:
+            if self.testStep and len(self.testStep) > 1:
+                if not isinstance(self.testStep[1], str) and executeDirect:
+                    # This TestStepMaster-Instance should actually do something - activitites are described
+                    # in the TestExecutionSteps.
+                    # Otherwise there's only a classname in TestStep[0]
+                    self.executeDirect(self.testStep[1][GC.STRUCTURE_TESTSTEPEXECUTION])
+
+                    # Teardown makes only sense, when we actually executed something directly in here
+                    # Otherwise (if it was 1 or 2 Tab-stops more to the left) we'd take execution time without
+                    # having done anything
+                    self.teardown()
+        except Exception as e:
+            logger.warning(f"Uncought exception {e}")
+            utils.traceback(exception_in=e)
 
         self.statistics.update_teststep_sequence()
 
@@ -227,7 +231,8 @@ class TestStepMaster:
         if lActivity == "GOTOURL":
             self.browserSession.goToUrl(lValue)
         elif lActivity == "SETTEXT":
-            self.browserSession.findByAndSetText(xpath=xpath, css=css, id=id, value=lValue, timeout=lTimeout)
+            self.browserSession.findByAndSetText(xpath=xpath, css=css, id=id, value=lValue, timeout=lTimeout,
+                                                 optional=lOptional)
         elif lActivity == "SETTEXTIF":
             self.browserSession.findByAndSetTextIf(xpath=xpath, css=css, id=id, value=lValue, timeout=lTimeout,
                                                    optional=lOptional)
@@ -238,6 +243,10 @@ class TestStepMaster:
             if lValue:
                 self.browserSession.findByAndForceText(xpath=xpath, css=css, id=id, value=lValue, timeout=lTimeout,
                                                        optional=lOptional)
+        elif lActivity == "FORCETEXTJS":
+            if lValue:
+                self.browserSession.findByAndForceViaJS(xpath=xpath, css=css, id=id, value=lValue, timeout=lTimeout,
+                                                        optional=lOptional)
         elif lActivity == "SETANCHOR":
             if not lLocator:
                 self.anchor = None
@@ -261,9 +270,10 @@ class TestStepMaster:
                 lWindow = int(lWindow)
             self.browserSession.handleWindow(windowNumber=lWindow, timeout=lTimeout)
         elif lActivity == "CLICK":
-            self.browserSession.findByAndClick(xpath=xpath, css=css, id=id, timeout=lTimeout)
+            self.browserSession.findByAndClick(xpath=xpath, css=css, id=id, timeout=lTimeout, optional=lOptional)
         elif lActivity == "CLICKIF":
-            self.browserSession.findByAndClickIf(xpath=xpath, css=css, id=id, timeout=lTimeout, value=lValue)
+            self.browserSession.findByAndClickIf(xpath=xpath, css=css, id=id, timeout=lTimeout, value=lValue,
+                                                 optional=lOptional)
         elif lActivity == "PAUSE":
             self.browserSession.sleep(seconds=float(lValue))
         elif lActivity == "IF":
@@ -273,11 +283,14 @@ class TestStepMaster:
                                                     timeout=lTimeout)
 
             self.__doComparisons(lComparison=lComparison, value1=lValue, value2=lValue2)
-            logger.debug(f"IF-condition {lValue} {lComparison} {lValue2} evaluated to: {self.ifIsTrue} ")
+            logger.debug(f"IF-condition original Value: {original_value} (transformed: {lValue}) {lComparison} {lValue2} "
+                         f"evaluated to: {self.ifIsTrue} ")
         elif lActivity == "ELSE":
             if not self.ifIsTrue:
                 self.manageNestedCondition(condition=lActivity)
                 logger.debug("Executing ELSE-condition")
+            else:
+                self.ifIsTrue = False
         elif lActivity == "ENDIF":
             self.manageNestedCondition(condition=lActivity)
         elif lActivity == "REPEAT":
@@ -381,7 +394,7 @@ class TestStepMaster:
             if len(lValue2) > 0:
                 lValue2 = self.replaceVariables(lValue2, replaceFromDict=replaceFromDict)
         except Exception as ex:
-            logger.info(ex)
+            logger.warning(f"During replacement of variables an error happened: {ex}")
         return lValue, lValue2
 
     def __getIBAN(self, lValue, lValue2):
@@ -480,13 +493,18 @@ class TestStepMaster:
         if value2 == 'None':
             value2 = None
 
+        logger.debug(f"Evaluating IF-Condition: Value1 = {value1}, comparison={lComparison}, value2={value2}")
+
         if lComparison == "=":
             if value1 == value2:
                 self.manageNestedCondition(condition="IF", ifis=True)
             else:
                 self.manageNestedCondition(condition="IF", ifis=False)
         elif lComparison == "!=":
-            self.ifIsTrue = False if value1 == value2 else True
+            if value1 != value2:
+                self.manageNestedCondition(condition="IF", ifis=True)
+            else:
+                self.manageNestedCondition(condition="IF", ifis=False)
         elif lComparison == ">":
             if value1 > value2:
                 self.manageNestedCondition(condition="IF", ifis=True)
@@ -497,6 +515,16 @@ class TestStepMaster:
                 self.manageNestedCondition(condition="IF", ifis=True)
             else:
                 self.manageNestedCondition(condition="IF", ifis=False)
+        elif lComparison == ">=":
+            if value1 >= value2:
+                self.manageNestedCondition(condition="IF", ifis=True)
+            else:
+                self.manageNestedCondition(condition="IF", ifis=False)
+        elif lComparison == "<=":
+            if value1 <= value2:
+                self.manageNestedCondition(condition="IF", ifis=True)
+            else:
+                self.manageNestedCondition(condition="IF", ifis=False)
         elif not lComparison:  # Check only, if Value1 has a value.
             val = True if value1 else False
             self.manageNestedCondition(condition="IF", ifis=val)

+ 25 - 2
baangt/base/BrowserFactory.py

@@ -28,6 +28,8 @@ class BrowserFactory:
             if self.globalSettings.get('TC.' + GC.EXECUTION_NETWORK_INFO) == True else None
         self.browsersMobProxies = {}
 
+        self.callsPerBrowserInstance = {}
+
         self.__startRotatingProxies()
 
     def __startRotatingProxies(self):
@@ -75,9 +77,12 @@ class BrowserFactory:
                 self.browser[browserInstance].slowExecutionToggle()
             return self.browser[browserInstance]
         else:
-            if self.globalSettings.get("TC.RestartBrowser"):
+
+            lRestartBrowserSession = self.checkMaxBrowserInstanceCallsUsedToRestart(browserInstance)
+
+            if self.globalSettings.get("TC.RestartBrowser") or lRestartBrowserSession:
                 if browserInstance in self.browser.keys():
-                    logger.debug(f"Instance {browserInstance}: TC.RestartBrowser was set. Quitting old browser.")
+                    logger.debug(f"Instance {browserInstance}: TC.RestartBrowser was set or Threshold-Limit for Testcases reached. Quitting old browser.")
                     lBrowser = self.browser[browserInstance]
                     lBrowser.closeBrowser()
                     del self.browser[browserInstance]
@@ -116,6 +121,24 @@ class BrowserFactory:
                 logger.debug(f"Using existing instance of browser {browserInstance}")
             return self.browser[browserInstance]
 
+    def checkMaxBrowserInstanceCallsUsedToRestart(self, browserInstance):
+        """
+        for each browserInstance we record how often it was used by a testcase.
+        If there's a Number n RestartBrowserAfter Global parameter and we're higher than that,
+        we restart the browser.
+        :param browserInstance:
+        :return:
+        """
+        lRestartBrowserSession = False
+        self.callsPerBrowserInstance[browserInstance] = self.callsPerBrowserInstance.get(browserInstance, 0) + 1
+        if self.callsPerBrowserInstance[browserInstance] > int(
+                self.globalSettings.get("TC.RestartBrowserAfter", 99999999)):
+            lRestartBrowserSession = True
+            logger.debug(f"Reached threshold for browser restart in instance {browserInstance}. Restarting Browser.")
+            self.callsPerBrowserInstance[browserInstance] = 0
+
+        return lRestartBrowserSession
+
     @staticmethod
     def setBrowserWindowSize(lBrowserInstance: BrowserDriver, browserWindowSize):
         lBrowserInstance.setBrowserWindowSize(browserWindowSize)

+ 37 - 11
baangt/base/BrowserHandling/BrowserHandling.py

@@ -86,7 +86,7 @@ class BrowserDriver:
         self.takeTime("Browser Start")
         self.randomProxy = randomProxy
         self.browserName = browserName
-        self.bpid = []
+        self.browserProcessID = []
         lCurPath = Path(self.managedPaths.getOrSetDriverPath())
 
         if browserName in webDrv.BROWSER_DRIVERS:
@@ -99,14 +99,14 @@ class BrowserDriver:
             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.bpid.append(self.browserData.driver.capabilities.get("moz:processID"))
+                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.bpid.append(int(fp.readlines()[-1].split()[1]))
+                    self.browserProcessID.append(int(fp.readlines()[-1].split()[1]))
                 except Exception as ex:
                     logger.info(ex)
             elif GC.BROWSER_EDGE == browserName:
@@ -123,8 +123,8 @@ class BrowserDriver:
                                                         command_executor=GC.REMOTE_EXECUTE_URL,
                                                         desired_capabilities=desiredCapabilities)
             else:
-                # TODO add exception, this code should never be reached
-                pass
+                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)
 
@@ -218,8 +218,8 @@ class BrowserDriver:
         try:
             if self.browserData.driver:
                 try:
-                    if len(self.bpid) > 0:
-                        for bpid in self.bpid:
+                    if len(self.browserProcessID) > 0:
+                        for bpid in self.browserProcessID:
                             os.kill(bpid, signal.SIGINT)
                 except:
                     pass
@@ -387,7 +387,7 @@ class BrowserDriver:
         """
         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)
+        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
 
@@ -462,8 +462,8 @@ class BrowserDriver:
         @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)
+        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")
@@ -502,7 +502,8 @@ class BrowserDriver:
         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)
+            return self.findByAndClick(id=id, css=css, xpath=xpath, class_name=class_name, iframe=iframe,
+                                       timeout=timeout, optional=optional)
         else:
             return False
 
@@ -521,6 +522,31 @@ class BrowserDriver:
 
         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

+ 18 - 9
baangt/base/BrowserHandling/WebdriverFunctions.py

@@ -296,10 +296,12 @@ class WebdriverFunctions:
                     element.click()
                 elif command.upper() == GC.CMD_FORCETEXT:
                     element.clear()
-                    for i in range(0, NUMBER_OF_SEND_KEY_BACKSPACE):
-                        element.send_keys(keys.Keys.BACKSPACE)
-                    time.sleep(0.1)
+                    element.click()
+                    # element.send_keys(keys.Keys.CONTROL+"A")
+                    # for i in range(0, NUMBER_OF_SEND_KEY_BACKSPACE):
+                    #     element.send_keys(keys.Keys.BACKSPACE)
                     element.send_keys(value)
+                    element.send_keys(keys.Keys.TAB)
                 didWork = True
             except ElementClickInterceptedException as e:
                 logger.debug("doSomething: Element intercepted - retry")
@@ -310,7 +312,8 @@ class WebdriverFunctions:
                 if counter < COUNTER_LIMIT_RETRY:
                     time.sleep(0.2)
                 elif counter < COUNTER_LIMIT_ELEMENT_REF:
-                    begin, element = WebdriverFunctions.webdriver_refindElementAfterError(browserData, timeout)
+                    begin, element = WebdriverFunctions.webdriver_refindElementAfterError(browserData, timeout,
+                                                                                          optional=optional)
                 else:
                     raise Exceptions.baangtTestStepException(e)
             except NoSuchElementException as e:
@@ -325,7 +328,8 @@ class WebdriverFunctions:
                     time.sleep(0.2)
                 elif counter < COUNTER_LIMIT_ELEMENT_NOT_INTERACT:
                     logger.debug(f"Element not interactable {browserData.locatorType} {browserData.locator}, re-finding element")
-                    begin, element = WebdriverFunctions.webdriver_refindElementAfterError(browserData, timeout)
+                    begin, element = WebdriverFunctions.webdriver_refindElementAfterError(browserData, timeout,
+                                                                                          optional=optional)
                 else:
                     helper.browserHelper_log(logging.ERROR, f"Element not interactable {e}", browserData)
                     raise Exceptions.baangtTestStepException(e)
@@ -346,13 +350,18 @@ class WebdriverFunctions:
         return didWork
 
     @staticmethod
-    def webdriver_refindElementAfterError(browserData, timeout):
+    def webdriver_refindElementAfterError(browserData, timeout, optional=None):
+        if not optional:
+            optional = False
         element, _ = WebdriverFunctions.webdriver_tryAndRetry(browserData, timeout=timeout / 2, optional=True)
         if element:
             logger.debug(f"Re-Found element {browserData.locatorType}: {browserData.locator}, will retry ")
             begin = time.time()
         else:
-            raise Exceptions.baangtTestStepException(
-                f"Element {browserData.locatorType} {browserData.locator} couldn't be found. "
-                f"Tried to re-find it, but not element was not found")
+            if not optional:
+                raise Exceptions.baangtTestStepException(
+                    f"Element {browserData.locatorType} {browserData.locator} couldn't be found. "
+                    f"Tried to re-find it, but not element was not found")
+            else:
+                return None, None
         return begin, element

+ 1 - 1
baangt/base/CustGlobalConstants.py

@@ -1,6 +1,6 @@
 CUST_TOASTS = "Toasts"
 # CUST_TOASTS_ERROR = "ToastsError" --> Replaced by GC.TESTCASEERRORLOG
-VIGOGFNUMMER = "GF#"
+VIGOGFNUMMER = "VIGO-GF"
 SAPPOLNR = "SAP Polizzennr"
 PRAEMIE = "Prämie"
 POLNRHOST = "PolNR Host"

+ 9 - 2
baangt/base/ExportResults/ExportResults.py

@@ -121,7 +121,8 @@ class ExportResults:
                 self.statistics.send_statistics()
             except Exception as ex:
                 logger.debug(ex)
-        self.update_result_in_testrun()
+        if not self.testRunInstance.noCloneXls:
+            self.update_result_in_testrun()
 
     def __removeUnwantedFields(self):
         lListPasswordFieldNames = ["PASSWORD", "PASSWORT", "PASSW"]
@@ -593,12 +594,14 @@ class ExportResults:
 
     def update_result_in_testrun(self):
         # To update source testrun file
+        logger.debug("TestResult updating")
         testrun_column = self.dataRecords[0]["testcase_column"]
         if testrun_column:  # if testrun_column is greater than 0 that means testresult header is present in source file
+            logger.debug(f'Header for result update is {self.dataRecords[0]["testcase_column"]} in sheet ' \
+                         f'{self.dataRecords[0]["testcase_sheet"]} of file {self.dataRecords[0]["testcase_file"]}')
             testrun_file = load_workbook(self.dataRecords[0]["testcase_file"])
             testrun_sheet = testrun_file.get_sheet_by_name(self.dataRecords[0]["testcase_sheet"])
             for key, value in self.dataRecords.items():
-                print(value)
                 data = f"TestCaseStatus: {value['TestCaseStatus']}\r\n" \
                        f"Timestamp: {self.testRun_end}\r\n" \
                        f"Duration: {value['Duration']}\r\n" \
@@ -607,8 +610,12 @@ class ExportResults:
                        f"TestCase_UUID: {str(self.testcase_uuids[key])}\r\n\r\n"
                 old_value = testrun_sheet.cell(value["testcase_row"] + 1, value["testcase_column"]).value or ""
                 testrun_sheet.cell(value["testcase_row"] + 1, value["testcase_column"]).value = data + old_value
+                logger.debug(f'Result written in row {value["testcase_row"]} column {value["testcase_column"]}')
+            logger.debug("Saving Source TestRun file.")
             testrun_file.save(self.dataRecords[0]["testcase_file"])
             logger.info(f"Source TestRun file {self.dataRecords[0]['testcase_file']} updated.")
+        else:
+            logger.debug(f"No TestResult column found")
 
     def __writeCell(self, line, cellNumber, testRecordDict, fieldName, strip=False):
         if fieldName in testRecordDict.keys() and testRecordDict[fieldName]:

+ 48 - 237
baangt/base/HandleDatabase.py

@@ -1,13 +1,9 @@
 import logging
 from xlrd3 import open_workbook
-import itertools
 import json
 import baangt.base.CustGlobalConstants as CGC
 import baangt.base.GlobalConstants as GC
 from baangt.base.Utils import utils
-import baangt.TestSteps.Exceptions
-from pathlib import Path
-import xl2dict
 import re
 from random import randint
 from openpyxl import load_workbook
@@ -33,7 +29,7 @@ class Writer:
         column = 0
         sheet = self.workbook[sht]
         headers = next(sheet.rows)
-        for header in headers:  # checks if usecount header is present in sheet
+        for header in headers:  # finds the header position
             if "usecount" in str(header.value).lower():
                 column = headers.index(header) + 1
         if column:
@@ -72,6 +68,7 @@ class HandleDatabase:
         self.dataDict = []
         self.recordPointer = 0
         self.sheet_dict = {}
+        self.usecount = False
 
     def __buildRangeDict(self):
         """
@@ -137,7 +134,7 @@ class HandleDatabase:
         if not fileName:
             logger.critical(f"Can't open file: {fileName}")
             return
-
+        logger.debug(f"Reading excel file {fileName}...")
         book = open_workbook(fileName)
         sheet = book.sheet_by_name(sheetName)
 
@@ -165,10 +162,11 @@ class HandleDatabase:
             temp_dic["testcase_file"] = fileName
             temp_dic["testcase_column"] = testrun_index
             self.dataDict.append(temp_dic)
-        self.usecount_dict = {}  # used to maintain usecount limit record and verify if that non of the data cross limit
-        self.writer = Writer(fileName)  # Writer class object to save file only in end, which will save time
 
-    def update_datarecords(self, dataDict, fileName):
+    def update_datarecords(self, dataDict, fileName, sheetName, noCloneXls):
+        logger.debug("Updating prefix data...")
+        self.testDataGenerator = TestDataGenerator(fileName, sheetName=sheetName,
+                                                   from_handleDatabase=True, noUpdate=noCloneXls)
         for td in dataDict:
             temp_dic = dataDict[td]
             new_data_dic = {}
@@ -184,19 +182,25 @@ class HandleDatabase:
                             temp_dic[keys][start_index:end_index+1], data_to_replace_with
                         )
 
-                if str(temp_dic[keys])[:4] == "RRD_":
-                    rrd_string = self.__process_rrd_string(temp_dic[keys])
-                    rrd_data = self.__rrd_string_to_python(rrd_string[4:], fileName)
+                if str(temp_dic[keys])[:4].upper() == "RRD_":
+                    logger.debug(f"Processing rrd data - {temp_dic[keys]}")
+                    rrd_data = self.get_data_from_tdg(temp_dic[keys])
+                    self.testDataGenerator.usecount_dict[repr(rrd_data)]["use"] += 1
+                    self.testDataGenerator.update_usecount_in_source(rrd_data)
                     for data in rrd_data:
                         new_data_dic[data] = rrd_data[data]
-                elif str(temp_dic[keys][:4]) == "RRE_":
-                    rre_string = self.__process_rre_string(temp_dic[keys])
-                    rre_data = self.__rre_string_to_python(rre_string[4:])
+                    logger.debug(f"Data processed - {temp_dic[keys]}")
+                elif str(temp_dic[keys])[:4].upper() == "RRE_":
+                    logger.debug(f"Processing rre data - {temp_dic[keys]}")
+                    rre_data = self.get_data_from_tdg(temp_dic[keys])
+                    self.testDataGenerator.usecount_dict[repr(rre_data)]["use"] += 1
+                    self.testDataGenerator.update_usecount_in_source(rre_data)
                     for data in rre_data:
                         new_data_dic[data] = rre_data[data]
-                elif str(temp_dic[keys][:4]) == "RLP_":
+                    logger.debug(f"Data processed - {temp_dic[keys]}")
+                elif str(temp_dic[keys])[:4].upper() == "RLP_":
                     temp_dic[keys] = self.rlp_process(temp_dic[keys], fileName)
-                elif str(temp_dic[keys][:5]).upper() == "RENV_":
+                elif str(temp_dic[keys])[:5].upper() == "RENV_":
                     temp_dic[keys] = str(TestDataGenerator.get_env_variable(temp_dic[keys][5:]))
                 else:
                     try:
@@ -206,7 +210,21 @@ class HandleDatabase:
                         pass
             for key in new_data_dic:
                 temp_dic[key] = new_data_dic[key]
-        self.writer.save()  # saving source input file once everything is done
+        self.testDataGenerator.save_usecount()
+
+    def get_data_from_tdg(self, string):
+        data = self.testDataGenerator.data_generators(string)
+        if self.testDataGenerator.usecount_dict[repr(data[0])]["limit"]:
+            data = [d for d in data if self.testDataGenerator.usecount_dict[repr(d)]["use"
+                                                                ] < self.testDataGenerator.usecount_dict[repr(d)]["limit"]]
+        if len(data) > 1:
+            data = data[randint(0, len(data) - 1)]
+        elif len(data) == 1:
+            data = data[0]
+        else:
+            raise BaseException(f"Not enough data for {string}, please verify if data is present or usecount limit" \
+                                "has reached!!")
+        return data
 
     def rlp_process(self, string, fileName):
         # Will get real data from rlp_ prefix string
@@ -236,170 +254,12 @@ class HandleDatabase:
                 data = self.rlp_process(data, fileName)
         return data
 
-    def __compareEqualStageInGlobalsAndDataRecord(self, currentNewRecordDict:dict) -> bool:
-        """
-        As method name says, compares, whether Stage in Global-settings is equal to stage in Data Record,
-        so that this record might be excluded, if it's for the wrong Stage.
-
-        :param currentNewRecordDict: The current Record
-        :return: Boolean
-        """
-        lAppend = True
-        if self.globals.get(GC.EXECUTION_STAGE):
-            if currentNewRecordDict.get(GC.EXECUTION_STAGE):
-                if currentNewRecordDict[GC.EXECUTION_STAGE] != self.globals[GC.EXECUTION_STAGE]:
-                    lAppend = False
-        return lAppend
-
-    def __processRrd(self, sheet_name, data_looking_for, data_to_match: dict, sheet_dict=None, caller="RRD_",
-                     file_name=None):
-        """
-        For more detail please refer to TestDataGenerator.py
-        :param sheet_name:
-        :param data_looking_for:
-        :param data_to_match:
-        :return: dictionary of TargetData
-        """
-        sheet_dict = self.sheet_dict if sheet_dict is None else sheet_dict
-        matching_data = [list(x) for x in itertools.product(*[data_to_match[key] for key in data_to_match])]
-        assert sheet_name in sheet_dict, \
-            f"Excel file {file_name} doesn't contain {sheet_name} sheet. Please recheck. Called in '{caller}'"
-        base_sheet = sheet_dict[sheet_name]
-        data_lis = []
-        if type(data_looking_for) == str:
-            data_looking_for = data_looking_for.split(",")
-
-        usecount, limit, usecount_header = self.check_usecount(base_sheet[0])
-
-        for data in base_sheet:
-            dt = ""
-            if len(matching_data) == 1 and len(matching_data[0]) == 0:
-                if data_looking_for[0] == "*":
-                    dt = data
-                    data_lis.append(dt)
-                else:
-                    dt = {keys: data[keys] for keys in data_looking_for}
-                    data_lis.append(dt)
-            else:
-                if [data[key] for key in data_to_match] in matching_data:
-                    if data_looking_for[0] == "*":
-                        dt = data
-                        data_lis.append(dt)
-                    else:
-                        dt = {keys: data[keys] for keys in data_looking_for}
-                        data_lis.append(dt)
-            if dt:
-                if repr(dt) not in self.usecount_dict:
-                    if usecount_header:
-                        if data[usecount_header]:
-                            used_limit = int(data[usecount_header])
-                        else:
-                            used_limit = 0
-                    else:
-                        used_limit = 0
-                    self.usecount_dict[repr(dt)] = {
-                        "use": used_limit, "limit": limit, "index": base_sheet.index(data) + 2, "sheet_name": sheet_name
-                    }
-                else:
-                    if limit:
-                        if not self.usecount_dict[repr(dt)]["use"] < self.usecount_dict[repr(dt)]["limit"]:
-                            data_lis.remove(dt)
-        return data_lis
-
-    def check_usecount(self, data):
-        # used to find and return if their is usecount header and limit in input file
-        usecount = False
-        limit = 0
-        usecount_header = None
-        for header in data:
-            if "usecount" in header.lower():
-                usecount = True
-                usecount_header = header
-                if "usecount_" in header.lower():
-                    try:
-                        limit = int(header.lower().strip().split("count_")[1])
-                    except:
-                        limit = 0
-        return usecount, limit, usecount_header
-
-    def __rrd_string_to_python(self, raw_data, fileName):
-        """
-        Convert string to python data types
-        :param raw_data:
-        :return:
-        """
-        first_value = raw_data[1:-1].split(',')[0].strip()
-        second_value = raw_data[1:-1].split(',')[1].strip()
-        if second_value[0] == "[":
-            second_value = ','.join(raw_data[1:-1].split(',')[1:]).strip()
-            second_value = second_value[:second_value.index(']') + 1]
-            third_value = [x.strip() for x in ']'.join(raw_data[1:-1].split(']')[1:]).split(',')[1:]]
-        else:
-            third_value = [x.strip() for x in raw_data[1:-1].split(',')[2:]]
-        evaluated_list = ']],'.join(','.join(third_value)[1:-1].strip().split('],')).split('],')
-        if evaluated_list[0] == "":
-            evaluated_dict = {}
-        else:
-            evaluated_dict = {
-                splited_data.split(':')[0]: self.__splitList(splited_data.split(':')[1]) for splited_data in
-                evaluated_list
-            }
-        if second_value[0] == "[" and second_value[-1] == "]":
-            second_value = self.__splitList(second_value)
-        if first_value not in self.sheet_dict:
-            self.sheet_dict, _ = self.__read_excel(path=fileName)
-        processed_datas = self.__processRrd(first_value, second_value, evaluated_dict, file_name=fileName)
-        assert len(processed_datas)>0, f"No matching data for RRD_. Please check the input file {fileName}. Was searching for " \
-                                       f"{first_value}, {second_value} and {str(evaluated_dict)} " \
-                                       f"but didn't find anything. Also please check the usecount limit if their is any."
-        final_data = processed_datas[randint(0, len(processed_datas)-1)]
-        if repr(final_data) in self.usecount_dict:
-            self.usecount_dict[repr(final_data)]["use"] += 1
-            self.writer.write(
-                self.usecount_dict[repr(final_data)]["index"], self.usecount_dict[repr(final_data)]["use"],
-                self.usecount_dict[repr(final_data)]["sheet_name"]
-            )
-        return final_data
-
-    def __rre_string_to_python(self, raw_data):
-        """
-        Convert string to python data types
-        :param raw_data:
-        :return:
-        """
-        file_name = raw_data[1:-1].split(',')[0].strip()
-        sheet_dict, _ = self.__read_excel(file_name)
-        first_value = raw_data[1:-1].split(',')[1].strip()
-        second_value = raw_data[1:-1].split(',')[2].strip()
-        if second_value[0] == "[":
-            second_value = ','.join(raw_data[1:-1].split(',')[2:]).strip()
-            second_value = second_value[:second_value.index(']') + 1]
-            third_value = [x.strip() for x in ']'.join(raw_data[1:-1].split(']')[1:]).split(',')[1:]]
-        else:
-            third_value = [x.strip() for x in raw_data[1:-1].split(',')[3:]]
-        evaluated_list = ']],'.join(','.join(third_value)[1:-1].strip().split('],')).split('],')
-        if evaluated_list[0] == "":
-            evaluated_dict = {}
-        else:
-            evaluated_dict = {
-                splited_data.split(':')[0]: self.__splitList(splited_data.split(':')[1]) for splited_data in
-                evaluated_list
-            }
-        if second_value[0] == "[" and second_value[-1] == "]":
-            second_value = self.__splitList(second_value)
-        processed_datas = self.__processRrd(first_value, second_value, evaluated_dict, sheet_dict, caller="RRE_")
-        assert len(processed_datas)>0, f"No matching data for RRD_. Please check the input file. Was searching for " \
-                                       f"{first_value}, {second_value} and {str(evaluated_dict)} " \
-                                       f"but didn't find anything"
-        final_data = randint(0, len(processed_datas)-1)
-        return processed_datas[final_data]
-
     def __rlp_string_to_python(self, raw_data, fileName):
         # will convert rlp string to python
         sheetName = raw_data.split(',')[0].strip()
         headerName = raw_data.split(',')[1].strip().split('=')[0].strip()
         headerValue = raw_data.split(',')[1].strip().split('=')[1].strip()
-        all_sheets, main_sheet = self.__read_excel(path=fileName, sheet_name=sheetName)
+        all_sheets, main_sheet = self.testDataGenerator.read_excel(path=fileName, sheet_name=sheetName, return_json=True)
         data_list = []
         for data in main_sheet:
             main_value = data[headerName]
@@ -415,37 +275,6 @@ class HandleDatabase:
                 data_list.append(data)
         return data_list
 
-    def __process_rre_string(self, rre_string):
-        """
-        For more detail please refer to TestDataGenerator.py
-        :param rre_string:
-        :return:
-        """
-        processed_string = ','.join([word.strip() for word in rre_string.split(', ')])
-        match = re.match(
-            r"(RRE_(\(|\[))[\w\d\s\-./\\]+\.(xlsx|xls),[a-zA-z0-9\s]+,(\[?[a-zA-z\s,]+\]?|)|\*,\[([a-zA-z0-9\s]+:\[[a-zA-z0-9,\s]+\](,?))*\]",
-            processed_string)
-        err_string = f"{rre_string} not matching pattern RRE_(fileName, sheetName, TargetData," \
-                     f"[Header1:[Value1],Header2:[Value1,Value2]])"
-        assert match, err_string
-        return processed_string
-
-    def __process_rrd_string(self, rrd_string):
-        """
-        For more detail please refer to TestDataGenerator.py
-        :param rrd_string:
-        :return:
-        """
-        processed_string = ','.join([word.strip() for word in rrd_string.split(', ')])
-        match = re.match(
-            r"(RRD_(\(|\[))[a-zA-z0-9\s]+,(\[?[a-zA-z\s,]+\]?|)|\*,\[([a-zA-z0-9\s]+:\[[a-zA-z0-9,\s]+\](,?))*\]",
-            processed_string
-        )
-        err_string = f"{rrd_string} not matching pattern RRD_(sheetName,TargetData," \
-                     f"[Header1:[Value1],Header2:[Value1,Value2]])"
-        assert match, err_string
-        return processed_string
-
     def __process_rlp_string(self, rlp_string):
         processed_string = ','.join([word.strip() for word in rlp_string.split(', ')])
         match = re.match(
@@ -456,38 +285,20 @@ class HandleDatabase:
         assert match, err_string
         return processed_string
 
-    def __splitList(self, raw_data):
-        """
-        Will convert string list to python list.
-        i.e. "[value1,value2,value3]" ==> ["value1","value2","value3"]
-        :param raw_data: string of list
-        :return: Python list
+    def __compareEqualStageInGlobalsAndDataRecord(self, currentNewRecordDict:dict) -> bool:
         """
-        proccesed_datas = [data.strip() for data in raw_data[1:-1].split(",")]
-        return proccesed_datas
+        As method name says, compares, whether Stage in Global-settings is equal to stage in Data Record,
+        so that this record might be excluded, if it's for the wrong Stage.
 
-    def __read_excel(self, path, sheet_name=""):
-        """
-        For more detail please refer to TestDataGenerator.py
-        :param path: Path to raw data xlsx file.
-        :param sheet_name: Name of base sheet sheet where main input data is located. Default will be the first sheet.
-        :return: Dictionary of all sheets and data, Dictionary of base sheet.
+        :param currentNewRecordDict: The current Record
+        :return: Boolean
         """
-        wb = open_workbook(path)
-        sheet_lis = wb.sheet_names()
-        sheet_dict = {}
-        for sheet in sheet_lis:
-            xl_obj = xl2dict.XlToDict()
-            data = xl_obj.fetch_data_by_column_by_sheet_name(path,sheet_name=sheet)
-            sheet_dict[sheet] = data
-        if sheet_name == "":
-            base_sheet = sheet_dict[sheet_lis[0]]
-        else:
-            assert sheet_name in sheet_dict, f"Excel file {path} doesn't contain {sheet_name} sheet. Please recheck."
-            base_sheet = sheet_dict[sheet_name]
-        self.sheet_dict = sheet_dict
-        self.base_sheet = base_sheet
-        return sheet_dict, base_sheet
+        lAppend = True
+        if self.globals.get(GC.EXECUTION_STAGE):
+            if currentNewRecordDict.get(GC.EXECUTION_STAGE):
+                if currentNewRecordDict[GC.EXECUTION_STAGE] != self.globals[GC.EXECUTION_STAGE]:
+                    lAppend = False
+        return lAppend
 
     def readNextRecord(self):
         """

+ 4 - 3
baangt/base/TestRun/TestRun.py

@@ -38,7 +38,7 @@ class TestRun:
     """
 
     def __init__(self, testRunName, globalSettingsFileNameAndPath=None,
-                 testRunDict=None, uuid=uuid4(), executeDirect=True):  # -- API support: testRunDict --
+                 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
@@ -50,7 +50,7 @@ class TestRun:
         self.testRunDict = testRunDict
         self.globalSettingsFileNameAndPath = globalSettingsFileNameAndPath
         self.testRunName, self.testRunFileName = \
-            self._sanitizeTestRunNameAndFileName(testRunName)
+            self._sanitizeTestRunNameAndFileName(testRunName, executeDirect)
 
         # Initialize everything else
         self.apiInstance = None
@@ -73,6 +73,7 @@ class TestRun:
         # 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)
 
@@ -380,7 +381,7 @@ class TestRun:
         return utils.dynamicImportOfClasses(fullQualifiedImportName=fullQualifiedImportName)
 
     @staticmethod
-    def _sanitizeTestRunNameAndFileName(TestRunNameInput):
+    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)

+ 9 - 0
baangt/base/Utils.py

@@ -6,6 +6,7 @@ import ntpath
 import logging
 import json
 import sys
+import traceback
 from pathlib import Path
 from baangt.base.PathManagement import ManagedPaths
 
@@ -66,6 +67,14 @@ class utils:
         return lDictOut
 
     @staticmethod
+    def traceback(exception_in):
+
+        ex_traceback = exception_in.__traceback__
+        tb_lines = "\n".join([line.rstrip('\n') for line in
+                    traceback.format_exception(exception_in.__class__, exception_in, ex_traceback)])
+        logger.info(tb_lines)
+
+    @staticmethod
     def _loopList(listIn):
         listOut = []
         for item in listIn:

+ 6 - 0
baangt/ui/pyqt/globalSetting.json

@@ -197,6 +197,12 @@
             "type": "text",
             "default": "",
             "displayText": "AppRes2BaseXLS"
+        },
+        "TC.RestartBrowserAfter": {
+            "hint": "Enter a number of Testcases, after that the browser shall be restartet. Valid: e.g. '2'. Instead of '1' you could set the flag RestartBrowser.",
+            "type": "text",
+            "default": "",
+            "displayText": "RestBrowserAfter"
         }
     }
 }

+ 1 - 1
docs/conf.py

@@ -24,7 +24,7 @@ copyright = '2020, Bernhard Buhl'
 author = 'Bernhard Buhl'
 
 # The full version, including alpha/beta/rc tags
-release = '1.1.20'
+release = '1.2.3'
 
 
 # -- General configuration ---------------------------------------------------

BIN
examples/2bc8adcf-33fa-4f97-bdde-31f149bf86ee


BIN
examples/CompleteBaangtWebdemo.xlsx


+ 0 - 93
examples/globals_20200711_111318.json

@@ -1,93 +0,0 @@
-{
-    "exportFilesBasePath": {
-        "hint": "",
-        "type": "text",
-        "options": [
-            ""
-        ],
-        "displayText": "exportFilesBasePath",
-        "default": ""
-    },
-    "TC.Lines": "",
-    "TC.slowExecution": "True",
-    "TC.NetworkInfo": "True",
-    "TC.dontCloseBrowser": "True",
-    "TC.Browser": "CHROME",
-    "TC.Mobile": {
-        "hint": "True",
-        "type": "text",
-        "options": [
-            "True"
-        ],
-        "displayText": "TC.Mobile",
-        "default": "True"
-    },
-    "TC.MobileApp": {
-        "hint": "True",
-        "type": "text",
-        "options": [
-            "True"
-        ],
-        "displayText": "TC.MobileApp",
-        "default": "True"
-    },
-    "TC.platformName": {
-        "hint": "Android",
-        "type": "text",
-        "options": [
-            "Android"
-        ],
-        "displayText": "TC.platformName",
-        "default": "Android"
-    },
-    "TC.deviceName": {
-        "hint": "emulator-5554",
-        "type": "text",
-        "options": [
-            "emulator-5554"
-        ],
-        "displayText": "TC.deviceName",
-        "default": "emulator-5554"
-    },
-    "TC.platformVersion": {
-        "hint": "5",
-        "type": "text",
-        "options": [
-            "5"
-        ],
-        "displayText": "TC.platformVersion",
-        "default": "5"
-    },
-    "TC.app": {
-        "hint": "/baangt/examples/calculator.apk",
-        "type": "text",
-        "options": [
-            "/baangt/examples/calculator.apk"
-        ],
-        "displayText": "TC.app",
-        "default": "/baangt/examples/calculator.apk"
-    },
-    "TC.appPackage": {
-        "hint": "com.google.android.calculator",
-        "type": "text",
-        "options": [
-            "com.google.android.calculator"
-        ],
-        "displayText": "TC.appPackage",
-        "default": "com.google.android.calculator"
-    },
-    "TC.appWaitActivity": {
-        "hint": "com.android.calculator2.Calculator",
-        "type": "text",
-        "options": [
-            "com.android.calculator2.Calculator"
-        ],
-        "displayText": "TC.appWaitActivity",
-        "default": "com.android.calculator2.Calculator"
-    },
-    "TX.DEBUG": "True",
-    "RootPath": "/home/aguryev/freelancer/baangt/examples",
-    "Screenshots": "/home/aguryev/freelancer/baangt/examples/Screenshots",
-    "1TestResults": "/home/aguryev/freelancer/baangt/examples/1testoutput",
-    "0TestInput": "/home/aguryev/freelancer/baangt/examples/0testdateninput"
-}

+ 0 - 34
examples/globals_20200815_184400.json

@@ -1,34 +0,0 @@
-{
-    "TC.Lines": "1",
-    "TC.dontCloseBrowser": "False",
-    "TC.slowExecution": "False",
-    "TC.NetworkInfo": "False",
-    "TX.DEBUG": "True",
-    "TC.Browser": "FF",
-    "TC.BrowserAttributes": "",
-    "TC.ParallelRuns": "1",
-    "TC.BrowserWindowSize": "1024x768",
-    "TC.LogLevel": "Debug",
-    "Stage": "Test",
-    "SendMailTo": "",
-    "NotificationWithAttachment": "True",
-    "MsWebHook": "",
-    "SlackWebHook": "",
-    "TelegramBot": "",
-    "TelegramChannel": "",
-    "DeactivateStatistics": "False",
-    "Password": {
-        "hint": "Franzi1234",
-        "type": "text",
-        "options": [
-            "Franzi1234"
-        ],
-        "displayText": "Password",
-        "default": "Franzi1234"
-    },
-    "AR2BXLS": "CompleteBaangtWebdemo_ResultsCombined.xlsx,CombinedResults",
-    "RootPath": "/home/aguryev/freelancer/baangt/examples",
-    "Screenshots": "/home/aguryev/freelancer/baangt/examples/Screenshots",
-    "1TestResults": "/home/aguryev/freelancer/baangt/examples/1testoutput",
-    "0TestInput": "/home/aguryev/freelancer/baangt/examples/0testdateninput"
-}

+ 8 - 6
requirements.txt

@@ -7,6 +7,7 @@ faker>=4.0.2
 gevent>=1.5.0
 lxml>=4.5.0
 openpyxl>=3.0.3
+pandas>=1.1.1
 Pillow>=7.1.2
 pyperclip>=1.8.0
 pluggy>=0.13.1
@@ -21,10 +22,11 @@ wheel>=0.34.2
 xl2dict>=0.1.5
 xlrd3>=1.0.0
 XlsxWriter>=1.2.7
-numpy>=1.18.4
-jinja2>=2.11
-pymsteams>=0.1.12
+numpy>=1.19.2
+jinja2>=2.11.2
+pymsteams>=0.1.14
 slack-webhook>=1.0.3
-psutil>=5.7.0
-atlassian-python-api>=1.16.0
-icopy2xls>=0.0.3
+psutil>=5.7.2
+atlassian-python-api>=1.17.3
+icopy2xls>=0.0.4
+xlsclone>=0.0.5

+ 3 - 3
setup.py

@@ -6,7 +6,7 @@ if __name__ == '__main__':
 
     setuptools.setup(
         name="baangt",
-        version="1.1.20",
+        version="1.2.3",
         author="Bernhard Buhl",
         author_email="info@baangt.org",
         description="Open source Test Automation Suite for MacOS, Windows, Linux",
@@ -20,12 +20,12 @@ if __name__ == '__main__':
                           "dataclasses", "dataclasses-json",
                           "faker",  "gevent", "jinja2", "lxml",
                           "openpyxl",
-                          "Pillow", "pluggy", "pyperclip",  "pyQT5==5.14.2",
+                          "pandas", "Pillow", "pluggy", "pyperclip",  "pyQT5==5.14.2",
                           "requests", "requests-toolbelt",
                           "schwifty", "selenium", "sqlalchemy",
                           "urllib3", "psutil", "pymsteams", "slack-webhook",
                           "xl2dict", "xlrd3", "xlsxwriter", "atlassian-python-api",
-                          "icopy2xls"],
+                          "icopy2xls", "xlsclone"],
         classifiers=[
             "Programming Language :: Python :: 3",
             "License :: OSI Approved :: MIT License",

BIN
tests/0TestInput/RawTestData.xlsx


BIN
tests/0TestInput/ServiceTestInput/CompleteBaangtWebdemo_else.xlsx


BIN
tests/0TestInput/ServiceTestInput/CompleteBaangtWebdemo_else_error.xlsx


+ 12 - 14
tests/test_AddressCreate.py

@@ -1,19 +1,17 @@
 import pytest
+import baangt.base.GlobalConstants as GC
+from baangt.base.AddressCreate import AddressCreate
 
 
-def test_getRandomAddress():
-    import baangt.base.GlobalConstants as GC
-    from baangt.base.AddressCreate import AddressCreate
+@pytest.mark.parametrize("CountryCode, PostalCode", [("AT", "1020"), ("CY", "6020")])
+def test_getRandomAddress(CountryCode, PostalCode):
+    addressCreate = AddressCreate(addressFilterCriteria={GC.ADDRESS_COUNTRYCODE:CountryCode})
+    address = addressCreate.returnAddress()
+    assert address[GC.ADDRESS_COUNTRYCODE] == CountryCode
+    assert address[GC.ADDRESS_POSTLCODE] == PostalCode
 
-    addressCreate = AddressCreate(addressFilterCriteria={GC.ADDRESS_COUNTRYCODE:"AT"})
 
-    assert addressCreate.returnAddress()[GC.ADDRESS_COUNTRYCODE] == "AT"
-    assert addressCreate.returnAddress()[GC.ADDRESS_POSTLCODE] == "1020"
-
-    addressCreate = AddressCreate(addressFilterCriteria={GC.ADDRESS_COUNTRYCODE:"CY"})
-
-    assert addressCreate.returnAddress()[GC.ADDRESS_COUNTRYCODE] == "CY"
-    assert addressCreate.returnAddress()[GC.ADDRESS_POSTLCODE] == "6020"
-
-if __name__ == '__main__':
-    test_getRandomAddress()
+def test_testStepAddressCreation():
+    from baangt.TestSteps.AddressCreation import AddressCreate
+    address = AddressCreate.returnAddress()
+    assert type(address) == dict

+ 75 - 0
tests/test_ApiHandling.py

@@ -0,0 +1,75 @@
+import pytest
+from unittest.mock import patch
+from baangt.TestSteps.Exceptions import baangtTestStepException
+from baangt.base.ApiHandling import ApiHandling
+
+
+class fake_response:
+    def __init__(self, **kwargs):
+        self.status_code = 200
+        self.headers = ""
+        self.process()
+
+    def get(self, **kwargs):
+        return self
+
+    def post(self, **kwargs):
+        return self
+
+    def process(self):
+        return {"success": 200, "headers": ""}
+
+    def json(self):
+        return '{"success": 200, "headers": ""}'
+
+    def close(self):
+        pass
+
+
+@pytest.fixture(scope="module")
+def apiHandling():
+    return ApiHandling()
+
+
+@patch("requests.Session", fake_response)
+def test_getSession(apiHandling):
+    apiHandling.getSession()
+    assert 1 == 1
+
+
+@pytest.mark.parametrize("sessionNumber", [(None), (1)])
+def test_getNewSession(sessionNumber, apiHandling):
+    if sessionNumber:
+        apiHandling.getNewSession(sessionNumber=sessionNumber)
+    else:
+        with pytest.raises(baangtTestStepException):
+            apiHandling.getNewSession()
+    assert 1 in apiHandling.session
+
+
+@patch.object(ApiHandling, "getSession", fake_response)
+def test_getURL(apiHandling):
+    apiHandling.setBaseURL("")
+    apiHandling.setEndPoint("")
+    apiHandling.getURL()
+    assert 1 == 1
+
+
+@pytest.mark.parametrize("status", [(200), (300)])
+def test_returnTestCaseStatus(status, apiHandling):
+    result = apiHandling.returnTestCaseStatus(status)
+    assert result == "OK" or result == "Failed"
+
+
+@patch.object(ApiHandling, "getSession", fake_response)
+def test_postURL(apiHandling):
+    apiHandling.setBaseURL("")
+    apiHandling.setEndPoint("")
+    apiHandling.postURL(content="{}", url="url")
+    assert 1 == 1
+
+
+def test_setLoginData(apiHandling):
+    apiHandling.setLoginData("user", "pass")
+    assert apiHandling.session[1].auth == ("user", "pass")
+    apiHandling.tearDown()

+ 20 - 0
tests/test_Append2BaseXls.py

@@ -0,0 +1,20 @@
+from baangt.base.ExportResults.Append2BaseXLS import Append2BaseXLS
+import pytest
+from unittest.mock import patch
+import icopy2xls
+
+
+class testRun:
+    def __init__(self):
+        self.globalSettings = {"AR2BXLS": "examples/CompleteBaangtWebdemo.xlsx,1;/fake/path/test.xlsx,1"}
+
+
+@pytest.fixture(scope="module")
+def testRunInstance():
+    return testRun()
+
+
+@patch.object(icopy2xls.Mover, "move")
+def test_A2BX(mock_mover, testRunInstance):
+    Append2BaseXLS(testRunInstance)
+    assert 1 == 1

+ 43 - 2
tests/test_SendReports.py

@@ -1,9 +1,9 @@
 from baangt.base.SendReports import Sender
 from pathlib import Path
 import configparser
-import requests
-import json
 import os
+from unittest.mock import patch
+from atlassian import Confluence
 
 
 def readConfig():
@@ -25,3 +25,44 @@ def test_telegram():
     messages = send_stats.sendTelegram(test=True)
     text = subject + "\n\n" + body
     assert text in messages
+
+
+def fake_response(text):
+    return '{"test@pytest": "Success"}'
+
+@patch.object(Confluence, "create_page")
+@patch.object(Confluence, "attach_file")
+def test_confluence(mock_attach, mock_create):
+    send_stats.globalSettings["Confluence-Base-Url"] = "xxxx"
+    send_stats.globalSettings["Confluence-Space"] = "xxxx"
+    send_stats.globalSettings["Confluence-Pagetitle"] = "xxxx"
+    send_stats.globalSettings["Confluence-Username"] = "xxxx"
+    send_stats.globalSettings["Confluence-Password"] = "xxxx"
+    send_stats.globalSettings["Confluence-Rootpage"] = "xxxx"
+    send_stats.globalSettings["Confluence-Remove_Headers"] = "xxxx"
+    send_stats.globalSettings["Confluence-Uploadoriginalfile"] = "xxxx"
+    send_stats.globalSettings["Confluence-Createsubpagesforeachxxentries"] = 11
+    send_stats.sendConfluence()
+    assert 1 == 1
+
+
+@patch("json.loads", fake_response)
+@patch("requests.post")
+def test_SendMail(mock_request):
+    send_stats.defaultSettings["SendMailTo"] = "test@pytest"
+    send_stats.sendMail()
+    assert mock_request.call_count == 1
+
+
+@patch("pymsteams.connectorcard")
+def test_SendMsTeams(mock_conn):
+    send_stats.defaultSettings["MsWebHook"] = "xxxx"
+    send_stats.sendMsTeam()
+    assert mock_conn.call_count == 1
+
+
+@patch("slack_webhook.Slack")
+def test_SendSlack(mock_conn):
+    send_stats.defaultSettings["SlackWebHook"] = "xxxx"
+    send_stats.sendSlack()
+    assert 1 == 1

+ 6 - 6
tests/test_TestDataGenerator.py

@@ -83,7 +83,7 @@ def test_write_to_wrong_Path():
 
 def test_rrd_simple_input():
     # Checks __processRrd to get dict to target data
-    rrd_output_dict = testDataGenerator._TestDataGenerator__processRrd(
+    rrd_output_dict = testDataGenerator._TestDataGenerator__processRrdRre(
         'Customers', 'Customer', {'Age group': ['30s', '40s'], 'Employment_status': ['employed']}
     )
     assert len(rrd_output_dict) > 0
@@ -93,7 +93,7 @@ def test_rrd_simple_input():
 
 def test_rrd_target_data_all():
     # Checks __processRrd to get dict to for all data of matching values
-    rrd_output_dict = testDataGenerator._TestDataGenerator__processRrd(
+    rrd_output_dict = testDataGenerator._TestDataGenerator__processRrdRre(
         'Customers', '*', {'Age group': ['30s', '40s'], 'Employment_status': ['employed']}
     )
     assert len(rrd_output_dict) > 0
@@ -103,14 +103,14 @@ def test_rrd_target_data_all():
 
 def test_rrd_no_data_to_match():
     # Checks __processRrd to get dict to for all data of when no value of matching is given
-    rrd_output_dict = testDataGenerator._TestDataGenerator__processRrd('PaymentTerms', '*', {})
+    rrd_output_dict = testDataGenerator._TestDataGenerator__processRrdRre('PaymentTerms', '*', {})
     assert len(rrd_output_dict) > 0
     for data in rrd_output_dict:
         print(data)
 
 def test_rre_simple_input():
     # Checks __processRrd to get dict to target data
-    rrd_output_dict = testDataGenerator._TestDataGenerator__data_generators(
+    rrd_output_dict = testDataGenerator.data_generators(
         "RRE_[examples/CompleteBaangtWebdemo.xlsx,CustomerData,[NameFirst,NameLast],[Stage:[Test]]]"
     )
     assert len(rrd_output_dict) == 5
@@ -120,7 +120,7 @@ def test_rre_simple_input():
 
 def test_rre_target_data_all():
     # Checks __processRrd to get dict to for all data of matching values
-    rrd_output_dict = testDataGenerator._TestDataGenerator__data_generators(
+    rrd_output_dict = testDataGenerator.data_generators(
         "RRE_[examples/CompleteBaangtWebdemo.xlsx,CustomerData,*,[Stage:[Test]]]"
     )
     assert len(rrd_output_dict) == 5
@@ -130,7 +130,7 @@ def test_rre_target_data_all():
 
 def test_rre_no_data_to_match():
     # Checks __processRrd to get dict to for all data of when no value of matching is given
-    rrd_output_dict = testDataGenerator._TestDataGenerator__data_generators(
+    rrd_output_dict = testDataGenerator.data_generators(
         "RRE_[examples/CompleteBaangtWebdemo.xlsx,CustomerData,*,[]"
     )
     assert len(rrd_output_dict) == 10

+ 62 - 1
tests/test_browserHandling.py

@@ -1,6 +1,19 @@
 import pytest
 import platform
 from baangt.base import GlobalConstants as GC
+from baangt.base.BrowserHandling.BrowserHandling import BrowserDriver
+from unittest.mock import patch, MagicMock
+
+
+browserName = "FIREFOX"
+desired_app = [{GC.MOBILE_PLATFORM_NAME: "Android"}, {GC.MOBILE_PLATFORM_NAME: "iOS"}]
+mobileApp = ["True", "False"]
+mobile_app_setting = {
+    GC.MOBILE_APP_URL: "test",
+    GC.MOBILE_APP_PACKAGE: "com.baangt.pytest",
+    GC.MOBILE_APP_ACTIVITY: "baangt.test",
+    GC.MOBILE_APP_BROWSER_PATH: "temp"
+}
 
 
 @pytest.fixture
@@ -8,7 +21,6 @@ def getdriver():
     """ This will return BrowserDriver instance
         for below test function
     """
-    from baangt.base.BrowserHandling.BrowserHandling import BrowserDriver
     return BrowserDriver()
 
 
@@ -20,6 +32,7 @@ def test_setZoomFactor(getdriver):
     getdriver.createNewBrowser()
     getdriver.goToUrl("https://www.baangt.org")
     # FInd element by class
+    getdriver.zoomFactorDesired = True
     getdriver.setZoomFactor(lZoomFactor=200)
 
     # TODO add check
@@ -193,3 +206,51 @@ def test_setBrowserWindowSizeWithX(getdriver):
     result = getdriver.setBrowserWindowSize("--800x600")
     getdriver.closeBrowser()
     assert isinstance(result, dict)
+
+
+@pytest.mark.parametrize(
+    "browserName, desired_app, mobileApp, mobile_app_setting",
+    [
+        (browserName, desired_app[0], mobileApp[0], mobile_app_setting),
+        (browserName, desired_app[0], mobileApp[1], mobile_app_setting),
+        (browserName, desired_app[1], mobileApp[0], mobile_app_setting),
+        (browserName, desired_app[1], mobileApp[1], mobile_app_setting)
+    ]
+)
+def test_mobileConnectAppium(browserName, desired_app, mobileApp, mobile_app_setting):
+    from baangt.base.BrowserHandling.WebdriverFunctions import WebdriverFunctions
+    wdf = WebdriverFunctions
+    with patch.dict(wdf.BROWSER_DRIVERS, {GC.BROWSER_APPIUM: MagicMock}):
+        BrowserDriver._mobileConnectAppium(browserName, desired_app, mobileApp, mobile_app_setting)
+    assert 1 == 1
+
+
+def test_handleIframe(getdriver):
+    getdriver.browserData = MagicMock()
+    getdriver.handleIframe("test")
+    assert 1 == 1
+    getdriver.iFrame = "test"
+    getdriver.handleIframe()
+    assert 1 == 1
+
+
+def test_checkLinks(getdriver):
+    getdriver.browserData = MagicMock()
+    getdriver.checkLinks()
+    assert 1 == 1
+
+
+def test_waitForPageLoadAfterButtonClick(getdriver):
+    getdriver.browserData = MagicMock()
+    getdriver.html = "test"
+    getdriver.waitForPageLoadAfterButtonClick()
+    assert 1 == 1
+
+
+@pytest.mark.parametrize("function", [("close"), ("closeall-0"), ("")])
+def test_handleWindow(function, getdriver):
+    getdriver.browserData = MagicMock()
+    getdriver.browserData.driver.window_handles = ["test"]
+    getdriver.handleWindow(function=function)
+    assert 1 == 1
+

+ 22 - 0
tests/test_browserHelperFunction.py

@@ -0,0 +1,22 @@
+import pytest
+import logging
+from unittest.mock import MagicMock, patch
+from baangt.base.BrowserHandling.BrowserHelperFunction import BrowserHelperFunction
+
+
+@pytest.mark.parametrize("desiredCapabilities", [({}), ({"seleniumGridIp": "0.0.0.0", "seleniumGridPort": "4444"})])
+def test_browserHelper_setSettingsRemoteV4(desiredCapabilities):
+    result = BrowserHelperFunction.browserHelper_setSettingsRemoteV4(desiredCapabilities)
+    assert len(result) == 3
+
+
+@pytest.mark.parametrize("logType", [(logging.ERROR), (logging.WARN), ("")])
+def test_browserHelper_log(logType):
+    BrowserHelperFunction.browserHelper_log(logType, "Log Text", MagicMock(), MagicMock, extra="test")
+    assert 1 == 1
+
+
+@patch("baangt.base.ProxyRotate.ProxyRotate.remove_proxy", MagicMock)
+def test_browserHelper_setProxyError():
+    BrowserHelperFunction.browserHelper_setProxyError({"ip": "127.0.0.1", "port": "4444"})
+    assert 1 == 1

+ 27 - 0
tests/test_testrun.py

@@ -1,4 +1,14 @@
 import pytest
+from unittest.mock import patch
+
+
+class subprocess_communicate:
+    def __init__(self, *stdout, **stderr):
+        pass
+
+    def communicate(self):
+        return b"firefox\n", 1
+
 
 @pytest.fixture
 def testrun_obj():
@@ -46,6 +56,23 @@ def test_setResult(testrun_obj):
     # check the result
     new_result = set(old_result[0]+1, old_result[1])
     assert new_result == testrun_obj.getSuccessAndError()
+
+
+@pytest.mark.parametrize("system, os_version", [
+    ("Linux", ["redhat-release"]),
+    ("Linux", ["debian_version"]),
+    ("Darwin", ["firefox"]),
+    ("Darwin", ["chrome"])
+])
+@patch("subprocess.Popen", subprocess_communicate)
+@patch("os.listdir")
+@patch("platform.system")
+def test_ExcelImporter(mock_plat, mock_list, system, os_version):
+    from baangt.base.TestRunExcelImporter import TestRunExcelImporter
+    mock_plat.return_value = system
+    mock_list.return_value = os_version
+    TestRunExcelImporter.get_browser(TestRunExcelImporter)
+    assert mock_plat.call_count > 0