TestRun.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. from baangt.base.BrowserHandling.BrowserHandling import BrowserDriver
  2. from baangt.base.ApiHandling import ApiHandling
  3. from baangt.base.ExportResults.ExportResults import ExportResults
  4. from baangt.base.Utils import utils
  5. from baangt.base import GlobalConstants as GC
  6. from baangt.base.TestRunExcelImporter import TestRunExcelImporter
  7. # needed - they'll be used dynamically later
  8. from baangt.TestSteps.TestStepMaster import TestStepMaster
  9. from baangt.TestCase.TestCaseMaster import TestCaseMaster
  10. from baangt.TestCaseSequence.TestCaseSequenceMaster import TestCaseSequenceMaster
  11. import logging
  12. from pathlib import Path
  13. import sys
  14. import os
  15. from baangt.base.Timing.Timing import Timing
  16. from baangt.base.TestRunUtils import TestRunUtils
  17. import time
  18. logger = logging.getLogger("pyC")
  19. class TestRun:
  20. """
  21. This is the main Class of Testexecution in the baangt Framework. It is usually started
  22. from baangt.py
  23. """
  24. def __init__(self, testRunName, globalSettingsFileNameAndPath=None):
  25. """
  26. @param testRunName: The name of the TestRun to be executed.
  27. @param globalSettingsFileNameAndPath: from where to read the <globals>.json
  28. """
  29. logger.info('init Testrun, id is {}'.format(id(self)))
  30. self.browser = {}
  31. self.apiInstance = None
  32. self.testType = None
  33. self.networkInfo = None
  34. self.kwargs = {}
  35. self.dataRecords = {}
  36. self.globalSettingsFileNameAndPath = globalSettingsFileNameAndPath
  37. self.globalSettings = {}
  38. self.testRunName, self.testRunFileName = \
  39. self._sanitizeTestRunNameAndFileName(testRunName)
  40. self.timing = Timing()
  41. self.timing.takeTime(GC.TIMING_TESTRUN) # Initialize Testrun Duration
  42. self.testRunUtils = TestRunUtils()
  43. self._initTestRun() # Loads the globals*.json file
  44. self.browserProxyAndServer = self.getBrowserProxyAndServer() \
  45. if self.globalSettings.get('TC.' + GC.EXECUTION_NETWORK_INFO) == 'True' else None
  46. self.testCasesEndDateTimes_1D = [] # refer to single execution
  47. self.testCasesEndDateTimes_2D = [[]] # refer to parallel execution
  48. self._loadJSONTestRunDefinitions()
  49. self._loadExcelTestRunDefinitions()
  50. self.executeTestRun()
  51. self.tearDown()
  52. def append1DTestCaseEndDateTimes(self, dt):
  53. self.testCasesEndDateTimes_1D.append(dt)
  54. def append2DTestCaseEndDateTimes(self, index, dt):
  55. [self.testCasesEndDateTimes_2D.append([]) for i in range(
  56. index + 1 - len(self.testCasesEndDateTimes_2D))] if index + 1 > len(
  57. self.testCasesEndDateTimes_2D) else None
  58. logger.info('before append index: {}, dt: {}, testCasesEndDateTimes_2D:{}'.format(index, dt, self.testCasesEndDateTimes_2D))
  59. self.testCasesEndDateTimes_2D[index].append(dt)
  60. logger.info('after append index: {}, dt: {}, testCasesEndDateTimes_2D:{}'.format(index, dt, self.testCasesEndDateTimes_2D))
  61. def tearDown(self):
  62. """
  63. Close browser (unless stated in the Globals to not do so) and API-Instances
  64. Take overall Time spent for the complete TestRun
  65. Write results of TestRun to output channel(s)
  66. """
  67. if not self.globalSettings.get("TC." + GC.EXECUTION_DONTCLOSEBROWSER):
  68. for browserInstance in self.browser.keys():
  69. self.browser[browserInstance].closeBrowser()
  70. self.timing.takeTime(GC.TIMING_TESTRUN)
  71. self.timing.takeTimeSumOutput()
  72. if self.apiInstance:
  73. self.apiInstance.tearDown()
  74. if self.browserProxyAndServer:
  75. network_info = self.browserProxyAndServer[0].har
  76. self.browserProxyAndServer[1].stop()
  77. self.kwargs['networkInfo'] = network_info
  78. if self.testCasesEndDateTimes_1D:
  79. self.kwargs['testCasesEndDateTimes_1D'] = self.testCasesEndDateTimes_1D
  80. logger.info('before prepared testCasesEndDateTimes_2D: {}'.format(self.testCasesEndDateTimes_2D))
  81. if self.testCasesEndDateTimes_2D and self.testCasesEndDateTimes_2D[0]:
  82. self.kwargs['testCasesEndDateTimes_2D'] = self.testCasesEndDateTimes_2D
  83. logger.info('after prepared testCasesEndDateTimes_2D: {}'.format(self.testCasesEndDateTimes_2D))
  84. ExportResults(**self.kwargs)
  85. successful, error = self.getSuccessAndError()
  86. logger.info(f"Finished execution of Testrun {self.testRunName}. "
  87. f"{successful} Testcases successfully executed, {error} errors")
  88. print(f"Finished execution of Testrun {self.testRunName}. "
  89. f"{successful} Testcases successfully executed, {error} errors")
  90. def getSuccessAndError(self):
  91. """
  92. Returns number of successful and number of error test cases of the current test run
  93. @rtype: object
  94. """
  95. lError = 0
  96. lSuccess = 0
  97. for value in self.dataRecords.values():
  98. if value[GC.TESTCASESTATUS] == GC.TESTCASESTATUS_ERROR:
  99. lError += 1
  100. elif value[GC.TESTCASESTATUS] == GC.TESTCASESTATUS_SUCCESS:
  101. lSuccess += 1
  102. return lSuccess, lError
  103. def getAllTestRunAttributes(self):
  104. return self.testRunUtils.getCompleteTestRunAttributes(self.testRunName)
  105. def getBrowser(self, browserInstance=1, browserName=None, browserAttributes=None, mobileType=None, mobileApp=None, desired_app=None ,mobile_app_setting=None):
  106. """
  107. This method is called whenever a browser instance (existing or new) is needed. If called without
  108. parameters it will create one instance of Firefox (geckodriver).
  109. if global setting TC.EXECUTION_SLOW is set, inform the browser instance about it.
  110. @param browserInstance: Number of the requested browser instance. If none is provided, always the default
  111. browser instance will be returned
  112. @param browserName: one of the browser names (e.g. FF, Chrome) from GC.BROWSER*
  113. @param browserAttributes: optional Browser Attributes
  114. @return: the browser instance of base class BrowserDriver
  115. """
  116. if mobileType == 'True' :
  117. logger.info(f"opening new Appium instance {browserInstance} of Appium browser {browserName}")
  118. self._getBrowserInstance(browserInstance=browserInstance)
  119. browser_proxy = self.browserProxyAndServer[0] if self.browserProxyAndServer else None
  120. self.browser[browserInstance].createNewBrowser(mobileType=mobileType,
  121. mobileApp=mobileApp,
  122. desired_app=desired_app,
  123. mobile_app_setting = mobile_app_setting,
  124. browserName=browserName,
  125. desiredCapabilities=browserAttributes,
  126. browserProxy=browser_proxy,
  127. browserInstance=browserInstance)
  128. if self.globalSettings.get("TC." + GC.EXECUTION_SLOW):
  129. self.browser[browserInstance].slowExecutionToggle()
  130. return self.browser[browserInstance]
  131. else:
  132. if browserInstance not in self.browser.keys():
  133. logger.info(f"opening new instance {browserInstance} of browser {browserName}")
  134. self._getBrowserInstance(browserInstance=browserInstance)
  135. browser_proxy = self.browserProxyAndServer[0] if self.browserProxyAndServer else None
  136. self.browser[browserInstance].createNewBrowser(mobileType=mobileType,
  137. mobileApp=mobileApp,
  138. desired_app=desired_app,
  139. mobile_app_setting = mobile_app_setting,
  140. browserName=browserName,
  141. desiredCapabilities=browserAttributes,
  142. browserProxy=browser_proxy,
  143. browserInstance=browserInstance)
  144. if self.globalSettings.get("TC." + GC.EXECUTION_SLOW):
  145. self.browser[browserInstance].slowExecutionToggle()
  146. else:
  147. logger.debug(f"Using existing instance of browser {browserInstance}")
  148. return self.browser[browserInstance]
  149. def _getBrowserInstance(self, browserInstance):
  150. self.browser[browserInstance] = BrowserDriver(timing=self.timing,
  151. screenshotPath=self.globalSettings[GC.PATH_SCREENSHOTS])
  152. def downloadBrowserProxy(self):
  153. pass
  154. def getBrowserProxyAndServer(self):
  155. from browsermobproxy import Server
  156. server = Server(os.getcwd() + GC.BROWSER_PROXY_PATH)
  157. logger.info("Starting browsermob proxy")
  158. server.start()
  159. time.sleep(1)
  160. proxy = server.create_proxy()
  161. time.sleep(1)
  162. return proxy, server
  163. def getAPI(self):
  164. if not self.apiInstance:
  165. self.apiInstance = ApiHandling()
  166. return self.apiInstance
  167. def setResult(self, recordNumber, dataRecordResult):
  168. logger.debug(f"Received new result for Testrecord {recordNumber}")
  169. self.dataRecords[recordNumber] = dataRecordResult
  170. def executeTestRun(self):
  171. """
  172. Start TestcaseSequence
  173. TestCaseSequence is a sequence of Testcases. In the TestcaseSequence there's a sequential List of
  174. Testcases to be executed.
  175. Before the loop (executeDictSequenceOfClasses) variables inside the the testrun-definition are replaced
  176. by values from the globals-file (e.g. if you want to generally run with FF, but in a certain case you want to
  177. run with Chrome, you'd have FF in the Testrundefinition, but set parameter in globals_chrome.json accordingly
  178. (in this case {"TC.Browser": "CHROME"}. TC.-Prefix signals the logic to look for this variable ("Browser")
  179. inside the testcase definitions and replace it with value "CHROME".
  180. """
  181. self.testRunUtils.replaceGlobals(self.globalSettings)
  182. self.executeDictSequenceOfClasses(
  183. self.testRunUtils.getCompleteTestRunAttributes(self.testRunName)[GC.STRUCTURE_TESTCASESEQUENCE],
  184. counterName=GC.STRUCTURE_TESTCASESEQUENCE)
  185. def executeDictSequenceOfClasses(self, dictSequenceOfClasses, counterName, **kwargs):
  186. """
  187. This is the main loop of the TestCaseSequence, TestCases, TestStepSequences and TestSteps.
  188. The Sequence of which class instance to create is defined by the TestRunAttributes.
  189. Before instancgetBrowsering the class it is checked, whether the class was loaded already and if not, will be loaded
  190. (only if the classname is fully qualified (e.g baangt<projectname>.TestSteps.myTestStep).
  191. If the testcase-Status is already "error" (GC.TESTCASESTATUS_ERROR) we'll stop the loop.
  192. @param dictSequenceOfClasses: The list of classes to be instanced. Must be a dict of {Enum, Classname},
  193. can be also {enum: [classname, <whatEverElse>]}
  194. @param counterName: Which Structure element we're currently looping, e.g. "TestStep" (GC.STRUCTURE_TESTSTEP)
  195. @param kwargs: TestrunAttributes, this TestRun, the Timings-Instance, the datarecord
  196. """
  197. if not kwargs.get(GC.KWARGS_TESTRUNATTRIBUTES):
  198. kwargs[GC.KWARGS_TESTRUNATTRIBUTES] = self.getAllTestRunAttributes()
  199. if not kwargs.get(GC.KWARGS_TESTRUNINSTANCE):
  200. kwargs[GC.KWARGS_TESTRUNINSTANCE] = self
  201. logger.info('get into not kwargs.getGC.KWARGS_TESTRUNINSTANCE, id is {}'.format(id(self)))
  202. if not kwargs.get(GC.KWARGS_TIMING):
  203. kwargs[GC.KWARGS_TIMING] = self.timing
  204. for key, value in dictSequenceOfClasses.items():
  205. # If any of the previous steps set the testcase to "Error" - exit here.
  206. if kwargs.get(GC.KWARGS_DATA):
  207. if kwargs[GC.KWARGS_DATA][GC.TESTCASESTATUS] == GC.TESTCASESTATUS_ERROR:
  208. logger.info(f"TC is already in status Error - not processing steps {counterName}: {key}, {value}"
  209. f"and everything behind this step")
  210. return
  211. logger.info(f"Starting {counterName}: {key}, {value} ")
  212. kwargs[counterName] = key
  213. if isinstance(value, list):
  214. lFullQualified = value[0] # First List-Entry must hold the ClassName
  215. else:
  216. lFullQualified = value
  217. if "." in lFullQualified:
  218. l_class = TestRun.__dynamicImportClasses(lFullQualified)
  219. else:
  220. l_class = globals()[lFullQualified]
  221. l_class(**kwargs) # Executes the class __init__
  222. self.kwargs = kwargs
  223. def _initTestRun(self):
  224. self.loadJSONGlobals()
  225. if not self.globalSettings.get(GC.PATH_SCREENSHOTS,None):
  226. self.globalSettings[GC.PATH_SCREENSHOTS] = str(Path(self.globalSettingsFileNameAndPath
  227. ).parent.joinpath("Screenshots").expanduser())
  228. self.globalSettings[GC.PATH_EXPORT] = str(Path(self.globalSettingsFileNameAndPath
  229. ).parent.joinpath("1testoutput").expanduser())
  230. self.globalSettings[GC.PATH_IMPORT] = str(Path(self.globalSettingsFileNameAndPath
  231. ).parent.joinpath("0testdateninput").expanduser())
  232. self.globalSettings[GC.PATH_ROOT] = str(Path(self.globalSettingsFileNameAndPath
  233. ).parent.expanduser())
  234. def loadJSONGlobals(self):
  235. if self.globalSettingsFileNameAndPath:
  236. self.globalSettings = utils.openJson(self.globalSettingsFileNameAndPath)
  237. def _loadJSONTestRunDefinitions(self):
  238. if not self.testRunFileName:
  239. return
  240. if ".JSON" in self.testRunFileName.upper():
  241. data = utils.replaceAllGlobalConstantsInDict(utils.openJson(self.testRunFileName))
  242. self.testRunUtils.setCompleteTestRunAttributes(testRunName=self.testRunName,
  243. testRunAttributes=data)
  244. def _loadExcelTestRunDefinitions(self):
  245. if not self.testRunFileName:
  246. return
  247. if ".XLSX" in self.testRunFileName.upper():
  248. logger.info(f"Reading Definition from {self.testRunFileName}")
  249. lExcelImport = TestRunExcelImporter(FileNameAndPath=self.testRunFileName, testRunUtils=self.testRunUtils)
  250. lExcelImport.importConfig(self.globalSettings)
  251. @staticmethod
  252. def __dynamicImportClasses(fullQualifiedImportName):
  253. """
  254. Requires fully qualified Name of Import-Class and module,
  255. e.g. TestCaseSequence.TCS_VIGO.TCS_VIGO if the class TCS_VIGO
  256. is inside the Python-File TCS_VIGO
  257. which is inside the Module TestCaseSequence
  258. if name is not fully qualified, the ClassName must be identical with the Python-File-Name,
  259. e.g. TestSteps.Franzi will try to import TestSteps.Franzi.Franzi
  260. @param fullQualifiedImportName:
  261. @return: The class instance. If no class instance can be found the TestRun aborts hard with sys.exit
  262. """
  263. importClass = fullQualifiedImportName.split(".")[-1]
  264. if globals().get(importClass):
  265. return globals()[importClass] # Class already imported
  266. if fullQualifiedImportName.split(".")[-2:-1][0] == fullQualifiedImportName.split(".")[-1]:
  267. moduleToImport = ".".join(fullQualifiedImportName.split(".")[0:-1])
  268. else:
  269. moduleToImport = fullQualifiedImportName
  270. mod = __import__(moduleToImport, fromlist=importClass)
  271. logger.debug(f"Imported module {fullQualifiedImportName}, result was {str(mod)}")
  272. retClass = getattr(mod, importClass)
  273. if not retClass:
  274. logger.critical(f"Can't import module: {fullQualifiedImportName}")
  275. sys.exit("Critical Error in Class import - can't continue. "
  276. "Please maintain proper classnames in Testrundefinition.")
  277. return retClass
  278. @staticmethod
  279. def _sanitizeTestRunNameAndFileName(TestRunNameInput):
  280. """
  281. @param TestRunNameInput: The complete File and Path of the TestRun definition (JSON or XLSX).
  282. @return: TestRunName and FileName (if definition of testrun comes from a file (JSON or XLSX)
  283. """
  284. if ".XLSX" in TestRunNameInput.upper() or ".JSON" in TestRunNameInput.upper():
  285. lRunName = utils.extractFileNameFromFullPath(TestRunNameInput)
  286. lFileName = TestRunNameInput
  287. else:
  288. lRunName = TestRunNameInput
  289. lFileName = None
  290. return lRunName, lFileName
  291. if __name__ == '__main__':
  292. print(1)