uimain.py 62 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612
  1. # This Python file uses the following encoding: utf-8
  2. # if__name__ == "__main__":
  3. # pass
  4. # from baangt.ui.pyqt.uidesign import Ui_MainWindow
  5. from baangt.ui.pyqt.uiDesign import Ui_MainWindow
  6. from PyQt5 import QtWidgets, QtCore, QtGui
  7. from PyQt5.QtCore import pyqtSlot
  8. import os
  9. import glob
  10. import json
  11. from pathlib import Path
  12. import baangt.base.GlobalConstants as GC
  13. from baangt.base.Utils import utils
  14. import logging
  15. import configparser
  16. # import subprocess
  17. import sys
  18. from baangt.ui.pyqt import resources
  19. from baangt.ui.pyqt.settingsGlobal import GlobalSettings
  20. from baangt.ui.ImportKatalonRecorder import ImportKatalonRecorder
  21. import pyperclip
  22. import platform
  23. from baangt.base.PathManagement import ManagedPaths
  24. from uuid import uuid4
  25. from baangt.base.FilesOpen import FilesOpen
  26. from baangt.base.RuntimeStatistics import Statistic
  27. #from baangt.base.PathManagement import ManagedPaths
  28. from baangt.base.DownloadFolderMonitoring import DownloadFolderMonitoring
  29. from baangt.base.Cleanup import Cleanup
  30. from baangt.base.ResultsBrowser import ResultsBrowser
  31. import xlrd3 as xlrd
  32. from baangt.reports import Dashboard, Summary
  33. from baangt.TestDataGenerator.TestDataGenerator import TestDataGenerator
  34. from baangt.base.Utils import utils
  35. from threading import Thread
  36. from time import sleep
  37. import signal
  38. from datetime import datetime
  39. logger = logging.getLogger("pyC")
  40. NAME_PLACEHOLDER = '< Name >'
  41. VALUE_PLACEHOLDER = '< Value >'
  42. class PyqtKatalonUI(ImportKatalonRecorder):
  43. """ Subclass of ImportKatalonRecorder :
  44. Aim : To disable GUI created by PySimpleGui
  45. and initialize everything
  46. """
  47. def __init__(self, directory=None):
  48. self.managedPaths = ManagedPaths()
  49. if directory == None:
  50. directory = self.managedPaths.derivePathForOSAndInstallationOption()
  51. self.directory = directory
  52. self.clipboardText = ""
  53. self.outputText = ""
  54. self.window = None
  55. self.outputData = {}
  56. self.outputFormatted = []
  57. self.fileNameExport = None
  58. class MainWindow(Ui_MainWindow):
  59. """ BaangtUI : Logic implementation file for uidesign
  60. """
  61. switch_window = QtCore.pyqtSignal(str)
  62. close_obj = QtCore.pyqtSignal()
  63. def __init__(self):
  64. ''' Init the super class '''
  65. super().__init__()
  66. self.lTestRun = None
  67. self.configContents = None
  68. self.__log_state = 0
  69. self.managedPaths = ManagedPaths()
  70. self.directory = None
  71. self.configFile = None
  72. self.configFiles = []
  73. self.configContents = {}
  74. self.tempConfigFile = None
  75. self.testRunFile = None
  76. self.testRunFiles = []
  77. self.__execute_button_state = "idle"
  78. self.__result_file = ""
  79. self.__log_file = ""
  80. self.__open_files = 0
  81. self.TDGResult = ""
  82. self.dataFile = ""
  83. self.Sheets = None
  84. self.selectedSheet = None
  85. self.__log_file_monitor = None
  86. self.stopIcon = None
  87. self.run_process = None
  88. self.executeIcon = None
  89. self.configInstance = None
  90. self.katalonRecorder = None
  91. self.statistics = Statistic()
  92. self.checkBox1Label = None
  93. self.checkBox1CheckBox = None
  94. self.lineEdit1Label = None
  95. self.lineEdit1LineEdit = None
  96. self.comboBox1Label = None
  97. self.comboBox1ComboBox = None
  98. self.clean_dialog = None
  99. self.cleanup_logs = None
  100. self.cleanup_screenshots = None
  101. self.cleanup_status = None
  102. self.cleanup_days = None
  103. self.cleanup_reports = None
  104. self.cleanup_downloads = None
  105. self.queryResults = None
  106. self.clipboardText = None
  107. def setupUi(self, MainWindow, directory=None):
  108. ''' Setup the UI for super class and Implement the
  109. logic here we want to do with User Interface
  110. '''
  111. super().setupUi(MainWindow)
  112. if directory == None:
  113. directory = self.managedPaths.derivePathForOSAndInstallationOption()
  114. self.directory = directory
  115. self.katalonRecorder = PyqtKatalonUI(self.directory)
  116. # self.refreshNew()
  117. # self.setupBasePath(self.directory)
  118. self.readConfig()
  119. self.logSwitch.setChecked(self.__log_state)
  120. self.show_hide_logs()
  121. self.openFilesSwitch.setChecked(self.__open_files)
  122. # update logo and icon
  123. self.updateLogoAndIcon(MainWindow)
  124. # initialize Katalon Importer and Global Setting Page
  125. # Add Button Signals and Slot here
  126. self.browsePushButton_4.clicked.connect(self.browsePathSlot)
  127. self.InputFileButton.clicked.connect(self.inputFileSlot)
  128. self.OutputFileButton.clicked.connect(self.outputFileSlot)
  129. self.exitPushButton_4.clicked.connect(self.mainPageView)
  130. self.TDGButton.clicked.connect(self.testDataGenerator)
  131. self.ResultButton.clicked.connect(self.openTDGResult)
  132. self.SheetCombo.activated.connect(self.updateSheet)
  133. # self.executePushButton.clicked(self.executeTest)
  134. # Setting Page actions and triggered
  135. self.settingsPushButton_4.clicked.connect(self.settingView)
  136. self.okPushButton.clicked.connect(self.saveToFile)
  137. self.settingComboBox_4.activated.connect(self.updateSettings)
  138. self.testRunComboBox_4.activated.connect(self.updateRunFile)
  139. self.exitPushButton.clicked.connect(self.mainPageView)
  140. self.AddMorePushButton.clicked.connect(self.addMore)
  141. self.deleteLastPushButton.clicked.connect(self.deleteLast)
  142. self.saveAspushButton.clicked.connect(self.saveAsNewFile)
  143. self.executePushButton_4.clicked.connect(self.executeButtonClicked)
  144. # FileOpen buttons
  145. self.openResultFilePushButton_4.clicked.connect(self.openResultFile)
  146. self.openLogFilePushButton_4.clicked.connect(self.openLogFile)
  147. self.openTestFilePushButton_4.clicked.connect(self.openTestFile)
  148. self.InputFileOpen.clicked.connect(self.openInputFile)
  149. # Result Browser buttons
  150. self.actionQuery.triggered.connect(self.showQueryPage)
  151. self.queryMakePushButton.clicked.connect(self.makeResultQuery)
  152. self.queryExportPushButton.clicked.connect(self.exportResultQuery)
  153. self.openExportPushButton.clicked.connect(self.openRecentQueryResults)
  154. self.queryExitPushButton.clicked.connect(self.mainPageView)
  155. # Quit Event
  156. self.actionExit.triggered.connect(self.quitApplication)
  157. # Show Report Event
  158. self.actionReport.triggered.connect(self.showReport)
  159. # Cleanup action
  160. self.actionCleanup.triggered.connect(self.cleanup_dialog)
  161. # TestDataGenerator
  162. self.actionTestDataGen.triggered.connect(self.show_tdg)
  163. # Katalon triggered
  164. self.actionImport_Katalon.triggered.connect(self.show_katalon)
  165. self.exitPushButton_3.clicked.connect(self.exitKatalon)
  166. self.savePushButton_2.clicked.connect(self.saveTestCase)
  167. self.copyClipboard_2.clicked.connect(self.copyFromClipboard)
  168. self.TextIn_2.textChanged.connect(self.importClipboard)
  169. # log & open file switch
  170. self.logSwitch.clicked.connect(self.show_hide_logs)
  171. self.openFilesSwitch.clicked.connect(self.change_openFiles_state)
  172. QtCore.QMetaObject.connectSlotsByName(MainWindow)
  173. def closeEvent(self, event):
  174. print("closeevent")
  175. self.close_obj.emit()
  176. self.close()
  177. def close(self, event):
  178. print("inside close")
  179. try:
  180. self.run_process.kill()
  181. except:
  182. pass
  183. self.child.terminate()
  184. self.child.waitForFinished()
  185. event.accept()
  186. def saveInteractiveGuiConfig(self):
  187. """ Save Interactive Gui Config variables """
  188. config = configparser.ConfigParser()
  189. config["Default"] = {
  190. "path": self.directory,
  191. "testrun": self.testRunComboBox_4.currentText(),
  192. "globals": self.settingComboBox_4.currentText(),
  193. "logstate": self.__log_state,
  194. "openfiles": self.__open_files
  195. }
  196. with open(self.managedPaths.getOrSetIni().joinpath("baangt.ini"), "w" ) as configFile:
  197. config.write(configFile)
  198. def readConfig(self):
  199. """ Read existing baangt.ini file """
  200. config = configparser.ConfigParser()
  201. try:
  202. config.read(self.managedPaths.getOrSetIni().joinpath("baangt.ini"))
  203. self.directory = config["Default"]['path']
  204. self.testRunFile = config["Default"]['testrun']
  205. self.configFile = config["Default"]['globals']
  206. if 'logstate' in config["Default"]:
  207. self.__log_state = int(config["Default"]["logstate"])
  208. else:
  209. self.__log_state = 0
  210. if 'openfiles' in config["Default"]:
  211. self.__open_files = int(config["Default"]["openfiles"])
  212. else:
  213. self.__open_files = 0
  214. self.setupBasePath(self.directory)
  215. self.readContentofGlobals()
  216. except Exception as e:
  217. print("Exception in Main readConfig. Starting with defaults", e)
  218. self.directory = self.managedPaths.derivePathForOSAndInstallationOption().joinpath("examples")
  219. if not self.directory.is_dir():
  220. self.directory = str(self.managedPaths.derivePathForOSAndInstallationOption())
  221. else:
  222. self.directory = str(self.directory)
  223. self.setupBasePath(self.directory)
  224. def readContentofGlobals(self):
  225. """ This will read the content of config file """
  226. configInstance = GlobalSettings.getInstance()
  227. configInstance.addValue(self.configFile)
  228. self.configContents = configInstance.config
  229. if not self.configContents.get('TC.' + GC.DATABASE_LINES):
  230. key = 'TC.' + GC.DATABASE_LINES
  231. self.configContents[key] = ""
  232. if not self.configContents.get('TC.' + GC.EXECUTION_DONTCLOSEBROWSER):
  233. key = 'TC.' + GC.EXECUTION_DONTCLOSEBROWSER
  234. self.configContents[key] = ""
  235. if not self.configContents.get('TC.' + GC.EXECUTION_SLOW):
  236. key = 'TC.' + GC.EXECUTION_SLOW
  237. self.configContents[key] = ""
  238. @QtCore.pyqtSlot()
  239. def show_katalon(self):
  240. """ Display katalon panel for Test case preparation """
  241. self.stackedWidget.setCurrentIndex(2)
  242. self.statusMessage("Katalon Studio is triggered", 1000)
  243. @QtCore.pyqtSlot()
  244. def show_tdg(self):
  245. """ Display katalon panel for Test case preparation """
  246. self.stackedWidget.setCurrentIndex(3)
  247. self.statusMessage("TestDataGenerator is triggered", 1000)
  248. def updateLogoAndIcon(self, MainWindow):
  249. """ This function initialize logo and icon """
  250. logo_pixmap = QtGui.QPixmap(":/baangt/baangtlogo")
  251. logo_pixmap.scaled(300, 120, QtCore.Qt.KeepAspectRatio)
  252. self.logo_4.setPixmap(logo_pixmap)
  253. self.queryLogo.setPixmap(logo_pixmap)
  254. icon = QtGui.QIcon()
  255. icon.addPixmap(
  256. QtGui.QPixmap(":/baangt/baangticon"),
  257. QtGui.QIcon.Normal,
  258. QtGui.QIcon.Off
  259. )
  260. MainWindow.setWindowIcon(icon)
  261. self.mainPage.setStyleSheet(
  262. "QLineEdit { background-color: white; \n"
  263. " color: rgb(46, 52, 54); \n"
  264. "}"
  265. "QComboBox { background-color: white; \n"
  266. " color: rgb(46, 52, 54); \n"
  267. "}"
  268. "QButton { color: white; \n"
  269. "}"
  270. )
  271. self.settingPage.setStyleSheet(
  272. "QLineEdit { background-color: white; \n"
  273. " color: rgb(46, 52, 54); \n"
  274. "}"
  275. "QComboBox { background-color: white; \n"
  276. " color: rgb(46, 52, 54); \n"
  277. "}"
  278. "QLabel { font: 75 11pt 'Arial';\n"
  279. "}"
  280. "QButton { color: white; \n"
  281. "}"
  282. )
  283. self.katalonPage.setStyleSheet(
  284. "QButton { color: white; \n"
  285. "}"
  286. )
  287. MainWindow.resize(980, 480)
  288. def statusMessage(self, str, duration=1000):
  289. """ Display status message passed in Status Bar
  290. Default duration (ms) = 1000 (1 sec)
  291. """
  292. self.statusbar.showMessage(str, duration)
  293. def getSettingsAndTestFilesInDirectory(self, dirName):
  294. """ Scan for *.xlsx files and *.json files and
  295. update the testRunComboBox and settingsComboBox items
  296. """
  297. if not dirName and not os.path.isdir(dirName):
  298. dirName = os.getcwd()
  299. # for getting back to original directory
  300. orig_path = os.getcwd()
  301. os.chdir(dirName)
  302. # self.testRunFiles = glob.glob("*.xlsx")
  303. # self.configFiles = glob.glob("global*.json")
  304. self.testRunFiles = []
  305. self.configFiles = []
  306. fileList = glob.glob("*.json")
  307. fileList.extend(glob.glob("*.xlsx"))
  308. if not platform.system().lower() == 'windows':
  309. # On MAC and LINUX there may be also upper/lower-Case versions
  310. fileList.extend(glob.glob("*.JSON"))
  311. fileList.extend(glob.glob("*.XLSX"))
  312. for file in fileList:
  313. if file[0:6].lower() == 'global': # Global Settings for Testrun must start with global_*
  314. self.configFiles.append(file)
  315. else:
  316. self.testRunFiles.append(file)
  317. pass
  318. # get back to orig_dir
  319. os.chdir(orig_path)
  320. # update the combo box
  321. self.testRunComboBox_4.clear()
  322. self.settingComboBox_4.clear()
  323. # Also, disable Execute and Details Button
  324. self.executePushButton_4.setEnabled(False)
  325. self.settingsPushButton_4.setEnabled(False)
  326. # Add files in Combo Box
  327. self.testRunComboBox_4.addItems(
  328. sorted(self.testRunFiles, key=lambda x: x.lower())
  329. )
  330. self.settingComboBox_4.addItems(
  331. sorted(self.configFiles, key=lambda x: x.lower())
  332. )
  333. # set default selection to 0
  334. if len(self.testRunFiles) > 0:
  335. # if testrun file not in Combo box of TestRunFiles
  336. index = self.testRunComboBox_4.findText(
  337. self.testRunFile,
  338. QtCore.Qt.MatchFixedString
  339. )
  340. if self.testRunFile not in self.testRunFiles:
  341. self.testRunComboBox_4.setCurrentIndex(0)
  342. self.testRunFile = self.testRunComboBox_4.currentText()
  343. else:
  344. self.testRunComboBox_4.setCurrentIndex(index)
  345. self.statusMessage("testrun file: {}".format(self.testRunFile))
  346. # Activate the Execute Button
  347. self.executePushButton_4.setEnabled(True)
  348. if len(self.configFiles) > 0:
  349. # if config file not in list of ConfigListFiles
  350. index = self.settingComboBox_4.findText(
  351. self.configFile,
  352. QtCore.Qt.MatchFixedString
  353. )
  354. if self.configFile not in self.configFiles:
  355. self.settingComboBox_4.setCurrentIndex(0)
  356. # Activate the Settings Detail Button
  357. self.configFile = self.settingComboBox_4.currentText()
  358. else:
  359. self.settingComboBox_4.setCurrentIndex(index)
  360. self.statusMessage("value of self.configfile {}".format(
  361. self.configFile))
  362. self.settingsPushButton_4.setEnabled(True)
  363. self.updateSettings()
  364. def setupBasePath(self, dirPath=""):
  365. """ Setup Base path of Execution as per directory Path"""
  366. if not dirPath:
  367. # Set up base path to Baangt directory
  368. # Based on current File path ../../../
  369. dirPath = os.path.dirname(os.path.dirname(
  370. os.path.dirname(os.path.dirname(__file__))
  371. ))
  372. if not dirPath:
  373. dirPath = os.path.abspath(os.curdir)
  374. self.pathLineEdit_4.insert(dirPath)
  375. else:
  376. self.pathLineEdit_4.setText(dirPath)
  377. self.directory = dirPath
  378. self.getSettingsAndTestFilesInDirectory(dirPath)
  379. self.statusMessage("Current Path: {} ".format(dirPath), 2000)
  380. def setupFilePath(self, filePath=()):
  381. """ Setup Base path of Execution as per directory Path"""
  382. if os.path.exists(filePath[0]):
  383. dirPath = filePath[0]
  384. self.InputFileEdit.setText(dirPath)
  385. self.getSheets(dirPath)
  386. self.OutputFileEdit.setText(os.path.dirname(dirPath))
  387. self.statusMessage("Current File: {} ".format(dirPath), 2000)
  388. def getSheets(self, dirName):
  389. """ Scan for *.xlsx files and *.json files and
  390. update the testRunComboBox and settingsComboBox items
  391. """
  392. wb = xlrd.open_workbook(dirName)
  393. self.Sheets = wb.sheet_names()
  394. self.SheetCombo.clear()
  395. # Add files in Combo Box
  396. self.SheetCombo.addItems(self.Sheets)
  397. # set default selection to 0
  398. if len(self.Sheets) > 0:
  399. try:
  400. index = self.testRunComboBox_4.findText(
  401. self.selectedSheet,
  402. QtCore.Qt.MatchFixedString
  403. )
  404. except:
  405. self.selectedSheet = ""
  406. index = 0
  407. if self.selectedSheet not in self.Sheets:
  408. self.SheetCombo.setCurrentIndex(0)
  409. self.selectedSheet = self.SheetCombo.currentText()
  410. else:
  411. self.SheetCombo.setCurrentIndex(index)
  412. self.statusMessage("Sheet: {}".format(self.selectedSheet), 3000)
  413. # Activate the Execute Button
  414. self.TDGButton.setEnabled(True)
  415. @pyqtSlot()
  416. def quitApplication(self):
  417. """ This function will close the UI """
  418. buttonReply = QtWidgets.QMessageBox.question(
  419. self.centralwidget,
  420. "Close Confirmation ",
  421. "Are you sure to quit?",
  422. QtWidgets.QMessageBox.Yes |
  423. QtWidgets.QMessageBox.No,
  424. QtWidgets.QMessageBox.No
  425. )
  426. if buttonReply == QtWidgets.QMessageBox.Yes:
  427. QtWidgets.QApplication.exit()
  428. @pyqtSlot()
  429. def mainPageView(self):
  430. """ This function will redirect to main page """
  431. self.stackedWidget.setCurrentIndex(0)
  432. @pyqtSlot()
  433. def updateRunFile(self):
  434. """ this file will update the testRunFile selection
  435. """
  436. self.testRunFile = self.testRunComboBox_4.currentText()
  437. self.statusMessage("Test Run Changed to: {}".format(self.testRunFile), 3000)
  438. self.saveInteractiveGuiConfig()
  439. @pyqtSlot()
  440. def updateSettings(self):
  441. """ Update the settings Variable with content in fileName"""
  442. # Try to get full path
  443. # write changes to ini file
  444. self.configFile = os.path.join(self.directory,
  445. self.settingComboBox_4.currentText())
  446. self.saveInteractiveGuiConfig()
  447. self.statusMessage("Settings changed to: {}".format(self.configFile), 3000)
  448. self.readContentofGlobals()
  449. @pyqtSlot()
  450. def updateSheet(self):
  451. """ this file will update the testRunFile selection
  452. """
  453. self.selectedSheet = self.SheetCombo.currentText()
  454. self.statusMessage("Selected Sheet: {}".format(self.selectedSheet), 3000)
  455. def executeButtonClicked(self):
  456. self.__result_file = ""
  457. self.__log_file = ""
  458. self.__log_file_monitor = DownloadFolderMonitoring(self.managedPaths.getLogfilePath())
  459. if self.__execute_button_state == "idle":
  460. self.runTestRun()
  461. elif self.__execute_button_state == "running":
  462. self.stopButtonPressed()
  463. @pyqtSlot()
  464. def runTestRun(self):
  465. if not self.configFile:
  466. self.statusMessage("No Config File", 2000)
  467. return
  468. if not self.testRunFile:
  469. self.statusMessage("No test Run File selected", 2000)
  470. return
  471. self.__execute_button_state = "running"
  472. self.stopIcon = QtGui.QIcon(":/baangt/stopicon")
  473. self.executePushButton_4.setIcon(self.stopIcon)
  474. self.executePushButton_4.setIconSize(QtCore.QSize(28, 20))
  475. self.executePushButton_4.setStyleSheet("color: rgb(255, 255, 255); background-color: rgb(204, 0, 0);")
  476. self.clear_logs_and_stats()
  477. runCmd = self._getRunCommand()
  478. # show status in status bar
  479. self.statusMessage("Executing.....", 4000)
  480. if self.configContents.get("TX.DEBUG", False) == "True":
  481. from baangt.base.TestRun.TestRun import TestRun
  482. lUUID = uuid4()
  483. self.lTestRun = ""
  484. self.lTestRun = TestRun(f"{Path(self.directory).joinpath(self.testRunFile)}",
  485. globalSettingsFileNameAndPath=f'{Path(self.directory).joinpath(self.tempConfigFile)}', uuid=lUUID)
  486. self.processFinished(debug=True)
  487. else:
  488. logger.info(f"Running command: {runCmd}")
  489. self.run_process = QtCore.QProcess()
  490. self.run_process.setProcessChannelMode(QtCore.QProcess.MergedChannels)
  491. self.run_process.readyReadStandardOutput.connect(
  492. lambda: self.process_stdout(self.run_process.readAllStandardOutput()))
  493. self.run_process.readyReadStandardError.connect(
  494. lambda: self.logTextBox.appendPlainText(
  495. str(self.run_process.readAllStandardError().data().decode('iso-8859-1'))))
  496. self.run_process.finished.connect(self.processFinished)
  497. self.run_process.start(runCmd)
  498. self.statusbar.showMessage("Running.....",4000)
  499. @pyqtSlot()
  500. def stopButtonPressed(self):
  501. if platform.system() == "Windows":
  502. self.signalCtrl(self.run_process)
  503. try:
  504. os.remove(Path(self.directory).joinpath(self.tempConfigFile))
  505. except Exception as e:
  506. logger.warning(f"Tried to remove temporary file but seems to be not there: "
  507. f"{self.directory}/{self.tempConfigFile}")
  508. self.run_process.kill()
  509. QtWidgets.QApplication.quit()
  510. else:
  511. os.kill(self.run_process.processId(), signal.SIGINT)
  512. self.run_process.waitForFinished(3000)
  513. self.run_process.kill()
  514. def update_datafile(self):
  515. from baangt.base.TestRun.TestRun import TestRun
  516. testRunFile = f"{Path(self.directory).joinpath(self.testRunFile)}"
  517. globalsFile = self.configFile
  518. uu = uuid4()
  519. tr = TestRun(testRunFile, globalsFile, uuid=uu, executeDirect=False)
  520. tr._initTestRunSettingsFromFile()
  521. if "TC.TestDataFileName" in tr.globalSettings:
  522. self.dataFile = tr.globalSettings["TC.TestDataFileName"]
  523. else:
  524. tr._loadJSONTestRunDefinitions()
  525. tr._loadExcelTestRunDefinitions()
  526. self.dataFile = self.findKeyFromDict(tr.testRunUtils.testRunAttributes, "TestDataFileName")
  527. def findKeyFromDict(self, dic, key):
  528. if isinstance(dic, list):
  529. for data in dic:
  530. if isinstance(dic, list) or isinstance(dic, dict):
  531. result = self.findKeyFromDict(data, key)
  532. if result:
  533. return result
  534. elif isinstance(dic, dict):
  535. for k in dic:
  536. if k == key:
  537. return dic[k]
  538. elif isinstance(dic[k], list) or isinstance(dic[k], dict):
  539. result = self.findKeyFromDict(dic[k], key)
  540. if result:
  541. return result
  542. return ""
  543. def signalCtrl(self, qProcess, ctrlEvent=None):
  544. import win32console, win32process, win32api, win32con
  545. if ctrlEvent is None:
  546. ctrlEvent = win32con.CTRL_C_EVENT
  547. has_console = False
  548. try:
  549. win32console.AllocConsole()
  550. except win32api.error:
  551. has_console = True
  552. if not has_console:
  553. # free the dummy console
  554. try:
  555. win32console.FreeConsole()
  556. except win32api.error:
  557. return False
  558. if has_console:
  559. # preserve the console in a dummy process
  560. try:
  561. hProc, _, pid, _ = win32process.CreateProcess(
  562. None, "cmd.exe", None, None, True, win32con.DETACHED_PROCESS,
  563. None, 'c:\\', win32process.STARTUPINFO())
  564. win32console.FreeConsole()
  565. except win32api.error:
  566. return False
  567. try:
  568. # attach to the process's console and generate the event
  569. win32console.AttachConsole(qProcess.processId())
  570. # Add a fake Ctrl-C handler for avoid instant kill is this console
  571. win32api.SetConsoleCtrlHandler(None, True)
  572. win32console.GenerateConsoleCtrlEvent(ctrlEvent, 0)
  573. sleep(1)
  574. win32console.GenerateConsoleCtrlEvent(ctrlEvent, 0)
  575. win32console.FreeConsole()
  576. except win32api.error:
  577. return False
  578. if not has_console:
  579. # we have no console to restore
  580. return True
  581. try:
  582. # attach to the dummy process's console and end the process
  583. win32console.AttachConsole(pid)
  584. win32process.TerminateProcess(hProc, 1)
  585. except Exception as ex:
  586. return False
  587. return True
  588. @pyqtSlot()
  589. def processFinished(self, debug=False):
  590. buttonReply = QtWidgets.QMessageBox.question(
  591. self.centralwidget,
  592. "Baangt Interactive Starter ",
  593. "Test Run finished !!",
  594. QtWidgets.QMessageBox.Ok,
  595. QtWidgets.QMessageBox.Ok
  596. )
  597. if debug:
  598. self.__result_file = self.lTestRun.results.fileName
  599. self.statusMessage(f"Completed ", 3000)
  600. self.executeIcon = QtGui.QIcon(":/baangt/executeicon")
  601. self.executePushButton_4.setIcon(self.executeIcon)
  602. self.executePushButton_4.setIconSize(QtCore.QSize(28, 20))
  603. self.executePushButton_4.setStyleSheet("color: rgb(255, 255, 255); background-color: rgb(138, 226, 52);")
  604. self.__execute_button_state = "idle"
  605. try:
  606. self.__log_file = self.__log_file_monitor.getNewFiles()[0][0]
  607. except:
  608. pass
  609. # Remove temporary Configfile, that was created only for this run:
  610. try:
  611. os.remove(Path(self.directory).joinpath(self.tempConfigFile))
  612. except Exception as e:
  613. logger.warning(f"Tried to remove temporary file but seems to be not there: "
  614. f"{self.directory}/{self.tempConfigFile}")
  615. if self.__open_files:
  616. self.openResultFile()
  617. self.openLogFile()
  618. def process_stdout(self, obj):
  619. text = str(obj.data().decode('iso-8859-1'))
  620. if "ExportResults _ __init__ : Export-Sheet for results: " in text:
  621. lis = text[text.index("results: "):].split(" ")
  622. result_file = lis[1].strip()
  623. while len(result_file)>4:
  624. if result_file[-5:] != ".xlsx":
  625. result_file = result_file[:-1]
  626. else:
  627. break
  628. if result_file[-5:] == ".xlsx":
  629. logger.debug(f"Found result_file in the logs: {result_file}")
  630. self.__result_file = result_file
  631. elif "Logfile used: " in text:
  632. self.__log_file = text.split("used: ")[1].strip()
  633. if "||Statistic:" in text:
  634. lis = text.split('||')
  635. stat = lis[1][10:]
  636. stat_lis = stat.split('\n')
  637. log = ''.join([lis[0], lis[2]])
  638. for x in range(9):
  639. self.statisticTable.setItem(0, x, QtWidgets.QTableWidgetItem(stat_lis[x].split(': ')[1]))
  640. self.statisticTable.item(0,x).setTextAlignment(QtCore.Qt.AlignCenter)
  641. self.statisticTable.item(0, x).setFlags(
  642. self.statisticTable.item(0, x).flags() ^ QtCore.Qt.ItemIsSelectable)
  643. self.statisticTable.item(0, x).setFlags(
  644. self.statisticTable.item(0, x).flags() ^ QtCore.Qt.ItemIsEditable)
  645. if x == 3:
  646. self.statisticTable.item(0, 3).setBackground(QtGui.QColor(115, 210, 22))#QBrush(QtCore.Qt.green))
  647. elif x == 4:
  648. self.statisticTable.item(0, 4).setBackground(QtGui.QColor(204, 0, 0))#QBrush(QtCore.Qt.red))
  649. else:
  650. self.statisticTable.item(0, x).setBackground(QtGui.QBrush(QtCore.Qt.white))
  651. log.replace('\n', '')
  652. if not log.strip() == "":
  653. self.logTextBox.appendPlainText(log.strip())
  654. else:
  655. text = text.replace('\n', '')
  656. if not text.strip() == "":
  657. self.logTextBox.appendPlainText(text.strip())
  658. def _getRunCommand(self):
  659. """
  660. If bundled (e.g. in pyinstaller),
  661. then the executable is already sys.executable,
  662. otherwise we need to concatenate executable and
  663. Script-Name before we can start
  664. a subprocess.
  665. @return: Full path and filename to call Subprocess
  666. """
  667. lStart = sys.executable
  668. if "python" in sys.executable.lower():
  669. if len(Path(sys.argv[0]).parents) > 1:
  670. # This is a system where the path the the script is
  671. # given in sys.argv[0]
  672. lStart = f'"{lStart}"' + f' "{sys.argv[0]}"'
  673. else:
  674. # this is a system where we need to join os.getcwd()
  675. # and sys.argv[0] because the path is not given in sys.argv[0]
  676. lStart = f'"{lStart}"' + f' "{Path(os.getcwd()).joinpath(sys.argv[0])}"'
  677. else:
  678. lStart = f'"{lStart}"'
  679. self.__makeTempConfigFile()
  680. return f'{lStart} ' \
  681. f'--run="{Path(self.directory).joinpath(self.testRunFile)}" ' \
  682. f'--globals="{Path(self.directory).joinpath(self.tempConfigFile)}" --gui True'
  683. def __makeTempConfigFile(self):
  684. """
  685. Add parameters to the Config-File for this Testrun and
  686. save the file under a temporary name
  687. """
  688. self.configContents[GC.PATH_ROOT] = self.directory
  689. self.configContents[GC.PATH_SCREENSHOTS] = str(Path(
  690. self.directory).joinpath("Screenshots"))
  691. self.configContents[GC.PATH_EXPORT] = str(Path(
  692. self.directory).joinpath("1testoutput"))
  693. self.configContents[GC.PATH_IMPORT] = str(Path(
  694. self.directory).joinpath("0testdateninput"))
  695. self.tempConfigFile = MainWindow.__makeRandomFileName()
  696. self.saveContentsOfConfigFile(self.tempConfigFile)
  697. def saveContentsOfConfigFile(self, lFileName = None):
  698. if not lFileName:
  699. lFileName = self.configFile
  700. with open(str(Path(self.directory).joinpath(lFileName)), 'w') as outfile:
  701. json.dump(self.configContents, outfile, indent=4)
  702. @staticmethod
  703. def __makeRandomFileName():
  704. return "globals_" + utils.datetime_return() + ".json"
  705. @pyqtSlot()
  706. def settingView(self):
  707. """
  708. View settings Below Main Windows
  709. """
  710. self.statusMessage("Settings Page Opened")
  711. self.stackedWidget.setCurrentIndex(1)
  712. self.readConfigFile()
  713. self.drawSetting()
  714. @pyqtSlot()
  715. def browsePathSlot(self):
  716. """ Browse Folder Containing *.xlsx file for execution. And
  717. globals.json file for Test specific settings
  718. """
  719. # get path from pathLineEdit
  720. basepath = self.pathLineEdit_4.text()
  721. if not basepath:
  722. basepath = "./"
  723. options = QtWidgets.QFileDialog.Options()
  724. options |= QtWidgets.QFileDialog.DontUseNativeDialog
  725. dirName = QtWidgets.QFileDialog.getExistingDirectory(
  726. None,
  727. "Select Directory ",
  728. basepath,
  729. options=options
  730. )
  731. if dirName:
  732. # self.pathLineEdit.insert(dirName)
  733. self.setupBasePath(dirName)
  734. @pyqtSlot()
  735. def inputFileSlot(self):
  736. """ Browse Folder Containing *.xlsx file for execution. And
  737. globals.json file for Test specific settings
  738. """
  739. # get path from pathLineEdit
  740. basepath = self.InputFileEdit.text()
  741. if not basepath:
  742. basepath = "./"
  743. options = QtWidgets.QFileDialog.Options()
  744. options |= QtWidgets.QFileDialog.DontUseNativeDialog
  745. fileName = QtWidgets.QFileDialog.getOpenFileName(
  746. None,
  747. "Select File ",
  748. basepath,
  749. "(*.xlsx)",
  750. options=options
  751. )
  752. if fileName:
  753. self.setupFilePath(fileName)
  754. @pyqtSlot()
  755. def outputFileSlot(self):
  756. """ Browse Folder Containing *.xlsx file for execution. And
  757. globals.json file for Test specific settings
  758. """
  759. # get path from pathLineEdit
  760. basepath = self.OutputFileEdit.text()
  761. if not basepath:
  762. basepath = "./"
  763. options = QtWidgets.QFileDialog.Options()
  764. options |= QtWidgets.QFileDialog.DontUseNativeDialog
  765. dirName = QtWidgets.QFileDialog.getExistingDirectory(
  766. None,
  767. "Select Directory ",
  768. basepath,
  769. options=options
  770. )
  771. if dirName:
  772. # self.pathLineEdit.insert(dirName)
  773. self.OutputFileEdit.setText(dirName)
  774. # Settings Page
  775. # All action and function related to Setting page is below
  776. def readConfigFile(self):
  777. """ Read the configFile and update the configInstance """
  778. if self.configFile:
  779. # Compute full path
  780. fullpath = os.path.join(self.directory, self.configFile)
  781. if os.path.isfile(fullpath):
  782. self.configInstance = GlobalSettings.getInstance()
  783. self.configInstance.addValue(fullpath)
  784. @QtCore.pyqtSlot()
  785. def addMore(self):
  786. """ This function will popup a dialog box,
  787. User input the keword and a new row is added
  788. to Form Layout
  789. """
  790. # get total no of rows, it will be index for new row
  791. count = self.formLayout.rowCount()
  792. all_keys = self.configInstance.globalconfig.items()
  793. # get keys values from formLayout
  794. formlayoutItems = self.parseFormLayout()
  795. # convert to dict, to fix unhashable type: dict error
  796. all_keys = dict(all_keys)
  797. # unused keys : globalconfig keys - formlayout keys
  798. keys = [d for d in all_keys.keys() if d not in formlayoutItems]
  799. # Now prepare key and displayText pair
  800. displayTextPairs = [
  801. (k, v['displayText'])
  802. for k, v in all_keys.items()
  803. if k in keys]
  804. # to store keys for displayText list
  805. shownkeys = [p[0] for p in displayTextPairs]
  806. # to store displayTextList
  807. shownvalues = [p[1] for p in displayTextPairs]
  808. item, okPressed = QtWidgets.QInputDialog.getItem(
  809. None,
  810. "New Parameter ",
  811. "Parameter Name",
  812. shownvalues,
  813. 0,
  814. True
  815. )
  816. if item and okPressed:
  817. if item in shownvalues:
  818. # get keys using index of item selection
  819. key = shownkeys[shownvalues.index(item)]
  820. else:
  821. # this is the key still not added in globalSettings
  822. key = item
  823. value = self.configInstance.globalconfig.get(
  824. key,
  825. GlobalSettings.transformToDict(key, ""))
  826. self.addNewRow(count, key, value)
  827. @QtCore.pyqtSlot()
  828. def deleteLast(self):
  829. """ This function when call delete last row
  830. from the form layout
  831. """
  832. # get the index of last row, equals totalrow minus one
  833. count = self.formLayout.rowCount()
  834. # delete the count - 1 th row
  835. if count > 0:
  836. self.formLayout.removeRow(count - 1)
  837. @QtCore.pyqtSlot()
  838. def saveAsNewFile(self):
  839. """ This will ask new file to save data """
  840. # save recent changes to config
  841. self.saveValue()
  842. # ask for fileName
  843. options = QtWidgets.QFileDialog.Options()
  844. options |= QtWidgets.QFileDialog.DontUseNativeDialog
  845. filename = QtWidgets.QFileDialog.getSaveFileName(
  846. None,
  847. "Save Global Setting File",
  848. self.directory,
  849. "JsonFile (*.json)",
  850. "",
  851. options=options
  852. )
  853. newFile = filename[0]
  854. if newFile:
  855. if not os.path.basename(newFile).endswith(".json"):
  856. newFile = os.path.join(
  857. os.path.dirname(newFile),
  858. os.path.basename(newFile) + ".json"
  859. )
  860. data = {}
  861. for key, value in self.configInstance.config.items():
  862. data[key] = value
  863. with open(newFile, 'w') as f:
  864. json.dump(data, f, indent=4)
  865. @QtCore.pyqtSlot()
  866. def saveToFile(self):
  867. """ Save the content to file"""
  868. # call saveFile before saving to File
  869. self.saveValue()
  870. data = {}
  871. for key, value in self.configInstance.config.items():
  872. data[key] = value
  873. if not self.configFile:
  874. # Open Dialog box to save file
  875. options = QtWidgets.QFileDialog.Options()
  876. options |= QtWidgets.QFileDialog.DontUseNativeDialog
  877. filename = QtWidgets.QFileDialog.getSaveFileName(
  878. None,
  879. "Save Global Setting File",
  880. self.directory,
  881. "JsonFile (*.json)",
  882. "",
  883. options=options
  884. )
  885. self.configFile = filename[0]
  886. if self.configFile:
  887. if not os.path.basename(self.configFile).endswith(".json"):
  888. self.configFile = os.path.join(
  889. os.path.dirname(self.configFile),
  890. os.path.basename(self.configFile) + ".json"
  891. )
  892. fullpath = self.configFile
  893. if not os.path.isabs(self.configFile):
  894. if self.directory:
  895. fullpath = os.path.join(self.directory, self.configFile)
  896. else:
  897. self.directory = os.getcwd() # .managedPaths.derivePathForOSAndInstallationOption()
  898. fullpath = os.path.join(self.directory, self.configFile)
  899. with open(fullpath, 'w') as f:
  900. json.dump(data, f, indent=4)
  901. self.drawSetting()
  902. self.readContentofGlobals()
  903. self.mainPageView()
  904. def parseFormLayout(self):
  905. """ This function will parse form layout
  906. and return dictionary items
  907. """
  908. data = {}
  909. count = self.formLayout.rowCount()
  910. for d in range(count):
  911. # item = self.formLayout.takeRow(0)
  912. labelItem = self.formLayout.itemAt(
  913. d,
  914. QtWidgets.QFormLayout.LabelRole
  915. )
  916. fieldItem = self.formLayout.itemAt(
  917. d,
  918. QtWidgets.QFormLayout.FieldRole
  919. )
  920. # print(labelItem)
  921. # print(fieldItem)
  922. key = ""
  923. value = ""
  924. if isinstance(labelItem, QtWidgets.QWidgetItem):
  925. lablename = labelItem.widget()
  926. key = lablename.objectName()
  927. if isinstance(fieldItem, QtWidgets.QWidgetItem):
  928. fieldname = fieldItem.widget()
  929. if isinstance(fieldname, QtWidgets.QCheckBox):
  930. # get checked status
  931. value = str(fieldname.isChecked())
  932. elif isinstance(fieldname, QtWidgets.QComboBox):
  933. # get current Text
  934. value = fieldname.currentText()
  935. elif isinstance(fieldname, QtWidgets.QLineEdit):
  936. value = fieldname.text()
  937. if key:
  938. data[key] = value
  939. return data
  940. @QtCore.pyqtSlot()
  941. def saveValue(self):
  942. """ This simple function call parseFormlayout to get
  943. dictionary data and update the config value
  944. """
  945. # update the data to config instance
  946. data = self.parseFormLayout()
  947. if self.configInstance:
  948. self.configInstance.updateValue(data)
  949. # print(self.configInstance.config)
  950. else:
  951. print("No config instance ")
  952. self.configInstance = GlobalSettings.getInstance()
  953. self.configInstance.updateValue(data)
  954. def drawSetting(self):
  955. """ This will draw Setting based on data in configInstance
  956. """
  957. # We will use filtered dict key only
  958. # settings = self.configInstance.filterIniKey()
  959. # Remove all existing data to print
  960. n_rows = self.formLayout.rowCount()
  961. if n_rows > 0:
  962. # Delete all existing rows
  963. # print("Number of rows", n_rows)
  964. for d in range(n_rows):
  965. self.formLayout.removeRow(0)
  966. self.formLayout.update()
  967. # update the groupbox headlines
  968. if self.configFile:
  969. settingFile = self.configFile
  970. # compute full path
  971. fullpath = os.path.join(self.directory, self.configFile)
  972. if os.path.isfile(fullpath):
  973. settingFile = fullpath
  974. else:
  975. settingFile = "globalSetting.json"
  976. _translate = QtCore.QCoreApplication.translate
  977. self.groupBox.setTitle(
  978. _translate(
  979. "Form",
  980. "Settings in {}".format(
  981. os.path.basename(settingFile)
  982. )))
  983. # prepare settings here
  984. settings = {}
  985. for key, value in self.configInstance.config.items():
  986. if key in self.configInstance.globalconfig:
  987. settings[key] = self.configInstance.globalconfig[key]
  988. settings[key]['default'] = value
  989. else:
  990. settings[key] = GlobalSettings.transformToDict(key, value)
  991. # settings = self.configInstance.config
  992. count = 0
  993. for key, value in sorted(
  994. settings.items(),
  995. key=lambda x: x[1]['type']
  996. ):
  997. self.addNewRow(count, key, value)
  998. count += 1
  999. def addNewRow(self, count, key, value):
  1000. """ This function will add new row at
  1001. count number with given key value pair
  1002. in formlayout
  1003. """
  1004. _translate = QtCore.QCoreApplication.translate
  1005. if value['type'] == 'bool':
  1006. # create check box
  1007. self.checkBox1Label = QtWidgets.QLabel(
  1008. self.scrollAreaWidgetContents
  1009. )
  1010. self.checkBox1Label.setObjectName(key)
  1011. self.checkBox1Label.setToolTip(value['hint'])
  1012. self.checkBox1Label.setText(
  1013. _translate("Form", value['displayText']))
  1014. self.checkBox1CheckBox = QtWidgets.QCheckBox(
  1015. self.scrollAreaWidgetContents)
  1016. # self.checkBox1CheckBox.setStyleSheet(
  1017. # "color: rgb(46, 52, 54);")
  1018. if isinstance(value['default'], bool):
  1019. # its bool type
  1020. self.checkBox1CheckBox.setChecked(value['default'])
  1021. elif isinstance(value['default'], str):
  1022. # its string type
  1023. if value['default'].lower() == "true":
  1024. self.checkBox1CheckBox.setChecked(True)
  1025. else:
  1026. self.checkBox1CheckBox.setChecked(False)
  1027. else:
  1028. # default
  1029. self.checkBox1CheckBox.setChecked(False)
  1030. self.formLayout.setWidget(
  1031. count,
  1032. QtWidgets.QFormLayout.LabelRole,
  1033. self.checkBox1Label)
  1034. self.formLayout.setWidget(
  1035. count,
  1036. QtWidgets.QFormLayout.FieldRole,
  1037. self.checkBox1CheckBox)
  1038. elif value['type'] == 'text':
  1039. self.lineEdit1Label = QtWidgets.QLabel(
  1040. self.scrollAreaWidgetContents)
  1041. self.lineEdit1Label.setToolTip(
  1042. _translate("Form", value['hint']))
  1043. self.lineEdit1Label.setObjectName(key)
  1044. self.lineEdit1Label.setText(
  1045. _translate("Form", value['displayText'])
  1046. )
  1047. self.lineEdit1LineEdit = QtWidgets.QLineEdit(
  1048. self.scrollAreaWidgetContents)
  1049. sizePolicy = QtWidgets.QSizePolicy(
  1050. QtWidgets.QSizePolicy.MinimumExpanding,
  1051. QtWidgets.QSizePolicy.Fixed)
  1052. sizePolicy.setHorizontalStretch(0)
  1053. sizePolicy.setVerticalStretch(0)
  1054. sizePolicy.setHeightForWidth(
  1055. self.lineEdit1LineEdit.sizePolicy().hasHeightForWidth())
  1056. self.lineEdit1LineEdit.setSizePolicy(sizePolicy)
  1057. self.lineEdit1LineEdit.setMinimumSize(QtCore.QSize(250, 0))
  1058. # self.lineEdit1LineEdit.setStyleSheet(
  1059. # "background-color: rgb(255, 255, 255);\n"
  1060. # "color: rgb(46, 52, 54);")
  1061. self.lineEdit1LineEdit.setText(
  1062. _translate("Form", value['default']))
  1063. self.formLayout.setWidget(
  1064. count,
  1065. QtWidgets.QFormLayout.LabelRole,
  1066. self.lineEdit1Label)
  1067. self.formLayout.setWidget(
  1068. count,
  1069. QtWidgets.QFormLayout.FieldRole,
  1070. self.lineEdit1LineEdit)
  1071. elif value['type'] == 'select':
  1072. self.comboBox1Label = QtWidgets.QLabel(
  1073. self.scrollAreaWidgetContents)
  1074. self.comboBox1Label.setObjectName(key)
  1075. self.comboBox1Label.setToolTip(value['hint'])
  1076. self.comboBox1Label.setText(
  1077. _translate("Form", value['displayText']))
  1078. self.formLayout.setWidget(
  1079. count,
  1080. QtWidgets.QFormLayout.LabelRole,
  1081. self.comboBox1Label)
  1082. self.comboBox1ComboBox = QtWidgets.QComboBox(
  1083. self.scrollAreaWidgetContents)
  1084. # self.comboBox1ComboBox.setStyleSheet(
  1085. # "color: rgb(46, 52, 54):\n"
  1086. # "background-color: rgb(255, 255, 255);")
  1087. self.comboBox1ComboBox.addItems(value['options'])
  1088. # set the Value
  1089. self.comboBox1ComboBox.setCurrentIndex(
  1090. self.comboBox1ComboBox.findText(
  1091. value['default'],
  1092. QtCore.Qt.MatchFixedString
  1093. ))
  1094. self.formLayout.setWidget(
  1095. count,
  1096. QtWidgets.QFormLayout.FieldRole,
  1097. self.comboBox1ComboBox)
  1098. # Katalon Recorder Page
  1099. # All setting and Action for Katalon Page is Below
  1100. @QtCore.pyqtSlot()
  1101. def exitKatalon(self):
  1102. """ this function will clear text from textIn and
  1103. TextOut and exit
  1104. """
  1105. self.TextIn_2.clear()
  1106. self.TextOut_2.clear()
  1107. self.stackedWidget.setCurrentIndex(0)
  1108. @QtCore.pyqtSlot()
  1109. def saveTestCase(self):
  1110. """ Use Existing ImportKatalonRecorder.saveTestCase internally to
  1111. save test cast to XLSX
  1112. """
  1113. options = QtWidgets.QFileDialog.Options()
  1114. options |= QtWidgets.QFileDialog.DontUseNativeDialog
  1115. filename = QtWidgets.QFileDialog.getSaveFileName(
  1116. None,
  1117. "Save Test Case File",
  1118. self.katalonRecorder.directory,
  1119. "",
  1120. "",
  1121. options=options
  1122. )
  1123. # resulted filename is in tuple, (fullpath, All Files(*))
  1124. if filename[0]:
  1125. self.katalonRecorder.directory = os.path.dirname(filename[0])
  1126. self.katalonRecorder.fileNameExport = os.path.basename(filename[0])
  1127. # save File
  1128. self.katalonRecorder.saveTestCase()
  1129. # Change the testrun file to newone and change directory
  1130. self.testRunFile = self.katalonRecorder.fileNameExport
  1131. self.setupBasePath(self.katalonRecorder.directory)
  1132. self.exitKatalon()
  1133. @QtCore.pyqtSlot()
  1134. def importClipboard(self):
  1135. """Extend: katalonRecorder.importClipboard internally """
  1136. # ignore last line as this will result unexpected
  1137. # Index out of range error
  1138. self.clipboardText = self.TextIn_2.toPlainText()
  1139. self.katalonRecorder.clipboardText = "\n".join([
  1140. text for text in self.clipboardText.split("\n")
  1141. if len(text.split("|")) > 2
  1142. ])
  1143. self.katalonRecorder.importClipboard()
  1144. self.TextOut_2.setPlainText(self.katalonRecorder.outputText)
  1145. @QtCore.pyqtSlot()
  1146. def copyFromClipboard(self):
  1147. """ Call ImportKatalonRecorder.importClipboard internally """
  1148. self.TextIn_2.setPlainText(pyperclip.paste())
  1149. self.importClipboard()
  1150. @QtCore.pyqtSlot()
  1151. def openResultFile(self):
  1152. """ Uses Files Open class to open Result file """
  1153. try:
  1154. if self.__result_file != "":
  1155. logger.debug(f"Opening ResultFile: {self.__result_file}")
  1156. filePathName = self.__result_file
  1157. fileName = os.path.basename(filePathName)
  1158. self.statusMessage(f"Opening file {fileName}", 3000)
  1159. FilesOpen.openResultFile(filePathName)
  1160. else:
  1161. self.statusMessage("No file found!", 3000)
  1162. except:
  1163. self.statusMessage("No file found!", 3000)
  1164. @QtCore.pyqtSlot()
  1165. def openLogFile(self):
  1166. """ Uses Files Open class to open Log file """
  1167. try:
  1168. if self.__result_file != "":
  1169. wb = xlrd.open_workbook(self.__result_file)
  1170. book = wb.sheet_by_name("Summary")
  1171. filePathName = book.row(7)[1].value.strip()
  1172. fileName = os.path.basename(filePathName)
  1173. self.statusMessage(f"Opening file {fileName}", 3000)
  1174. FilesOpen.openResultFile(filePathName)
  1175. else:
  1176. if self.__log_file != "":
  1177. filePathName = self.__log_file
  1178. fileName = os.path.basename(filePathName)
  1179. self.statusMessage(f"Opening file {fileName}", 3000)
  1180. FilesOpen.openResultFile(filePathName)
  1181. else:
  1182. self.statusMessage("No file found!", 3000)
  1183. except:
  1184. self.statusMessage("No file found!", 3000)
  1185. @QtCore.pyqtSlot()
  1186. def openTestFile(self):
  1187. """ Uses Files Open class to open Log file """
  1188. try:
  1189. filePathName = f"{Path(self.directory).joinpath(self.testRunFile)}"
  1190. fileName = os.path.basename(filePathName)
  1191. self.statusMessage(f"Opening file {fileName}", 3000)
  1192. FilesOpen.openResultFile(filePathName)
  1193. self.update_datafile()
  1194. if self.dataFile:
  1195. self.statusMessage(f"Opening file {self.dataFile}", 3000)
  1196. PathName = f"{Path(self.directory).joinpath(self.dataFile)}"
  1197. Name = os.path.basename(PathName)
  1198. FilesOpen.openResultFile(PathName)
  1199. except:
  1200. self.statusMessage("No file found!", 3000)
  1201. @QtCore.pyqtSlot()
  1202. def openInputFile(self):
  1203. """ Uses Files Open class to open Log file """
  1204. filePathName = self.InputFileEdit.text()
  1205. if len(filePathName) > 0:
  1206. fileName = os.path.basename(filePathName)
  1207. self.statusMessage(f"Opening file {fileName}", 3000)
  1208. FilesOpen.openResultFile(filePathName)
  1209. else:
  1210. self.statusMessage("Please select a file", 3000)
  1211. @QtCore.pyqtSlot()
  1212. def openTDGResult(self):
  1213. """ Uses Files Open class to open Log file """
  1214. if os.path.exists(self.TDGResult):
  1215. fileName = os.path.basename(self.TDGResult)
  1216. self.statusMessage(f"Opening file {fileName}", 3000)
  1217. FilesOpen.openResultFile(self.TDGResult)
  1218. else:
  1219. self.statusMessage("No result file found!", 3000)
  1220. @pyqtSlot()
  1221. def clear_logs_and_stats(self):
  1222. for x in range(9):
  1223. self.statisticTable.setItem(0, x, QtWidgets.QTableWidgetItem())
  1224. self.statisticTable.item(0,x).setBackground(QtGui.QBrush(QtCore.Qt.white))
  1225. self.logTextBox.clear()
  1226. QtCore.QCoreApplication.processEvents()
  1227. @pyqtSlot()
  1228. def show_hide_logs(self):
  1229. if self.logSwitch.isChecked():
  1230. self.logTextBox.show()
  1231. self.__log_state = 1
  1232. else:
  1233. self.logTextBox.hide()
  1234. self.__log_state = 0
  1235. self.saveInteractiveGuiConfig()
  1236. def change_openFiles_state(self):
  1237. if self.openFilesSwitch.isChecked():
  1238. self.__open_files = 1
  1239. else:
  1240. self.__open_files = 0
  1241. self.saveInteractiveGuiConfig()
  1242. #
  1243. # Browse Results actions
  1244. #
  1245. @pyqtSlot()
  1246. def showQueryPage(self):
  1247. #
  1248. # display Query Page
  1249. #
  1250. # setup query object
  1251. self.queryResults = ResultsBrowser()
  1252. # setup combo box lists
  1253. self.nameComboBox.clear()
  1254. self.nameComboBox.addItems([''] + self.queryResults.name_list())
  1255. self.stageComboBox.clear()
  1256. self.stageComboBox.addItems([''] + self.queryResults.stage_list())
  1257. # globals
  1258. for nameComboBox, valueComboBox in self.globalsOptions:
  1259. nameComboBox.clear()
  1260. nameComboBox.addItems([NAME_PLACEHOLDER] + self.queryResults.globals_names())
  1261. nameComboBox.currentIndexChanged.connect(self.onGlobalsNameChange)
  1262. valueComboBox.clear()
  1263. valueComboBox.addItems([VALUE_PLACEHOLDER])
  1264. # show the page
  1265. self.stackedWidget.setCurrentIndex(4)
  1266. self.statusMessage("Query Page is triggered", 1000)
  1267. @pyqtSlot(int)
  1268. def onGlobalsNameChange(self, index):
  1269. #
  1270. # loads globals values on name changed
  1271. #
  1272. combo = self.sender()
  1273. for nameComboBox, valueComboBox in self.globalsOptions:
  1274. if combo == nameComboBox:
  1275. valueComboBox.clear()
  1276. valueComboBox.addItems([VALUE_PLACEHOLDER] + self.queryResults.globals_values(combo.currentText()))
  1277. @pyqtSlot()
  1278. def makeResultQuery(self):
  1279. #
  1280. # makes query to results db
  1281. #
  1282. # get field data
  1283. name = self.nameComboBox.currentText() or None
  1284. stage = self.stageComboBox.currentText() or None
  1285. #date_from = datetime.strptime(self.dateFromInput.date().toString("yyyyMMdd"), "%Y%m%d")
  1286. #date_to = datetime.strptime(self.dateToInput.date().toString("yyyyMMdd"), "%Y%m%d")
  1287. date_from = self.dateFromInput.date().toString("yyyy-MM-dd")
  1288. date_to = self.dateToInput.date().toString("yyyy-MM-dd")
  1289. # get globals
  1290. globals_value = lambda v: v if v != VALUE_PLACEHOLDER else None
  1291. global_settings = {
  1292. nameComboBox.currentText(): globals_value(valueComboBox.currentText()) for nameComboBox, valueComboBox in filter(
  1293. lambda g: g[0].currentText() != NAME_PLACEHOLDER,
  1294. self.globalsOptions,
  1295. )
  1296. }
  1297. # make query
  1298. self.queryResults.query(
  1299. name=name,
  1300. stage=stage,
  1301. start_date=date_from,
  1302. end_date=date_to,
  1303. global_settings=global_settings,
  1304. )
  1305. # display status
  1306. if self.queryResults.query_set:
  1307. if self.queryResults.query_set.length > 1:
  1308. status = f'Found: {self.queryResults.query_set.length} records'
  1309. else:
  1310. status = f'Found: {self.queryResults.query_set.length} record'
  1311. else:
  1312. status = 'No record found'
  1313. self.queryStatusLabel.setText(QtCore.QCoreApplication.translate("MainWindow", status))
  1314. @pyqtSlot()
  1315. def exportResultQuery(self):
  1316. #
  1317. # exports query results
  1318. #
  1319. _translate = QtCore.QCoreApplication.translate
  1320. if self.queryResults.query_set:
  1321. #self.queryStatusLabel.setText(_translate("MainWindow", "Exporting results..."))
  1322. #sleep(1)
  1323. path_to_export = self.queryResults.export()
  1324. self.queryStatusLabel.setText(_translate("MainWindow", f"Exported to: {path_to_export}"))
  1325. FilesOpen.openResultFile(path_to_export)
  1326. else:
  1327. self.queryStatusLabel.setText(_translate("MainWindow", f"ERROR: No data to export"))
  1328. @pyqtSlot()
  1329. def openRecentQueryResults(self):
  1330. #
  1331. # opens recent query result file
  1332. #
  1333. try:
  1334. # get recent file
  1335. recent_export = max(
  1336. list(Path(self.managedPaths.getOrSetDBExportPath()).glob('*.xlsx')),
  1337. key=os.path.getctime,
  1338. )
  1339. FilesOpen.openResultFile(recent_export)
  1340. except Exception:
  1341. # no export file exists
  1342. self.statusMessage(f"No Result Export File to Show", 3000)
  1343. @pyqtSlot()
  1344. def cleanup_dialog(self):
  1345. self.clean_dialog = QtWidgets.QDialog(self.centralwidget)
  1346. self.clean_dialog.setWindowTitle("Cleanup")
  1347. vlay = QtWidgets.QVBoxLayout()
  1348. self.cleanup_logs = QtWidgets.QCheckBox()
  1349. self.cleanup_logs.setText("Logs")
  1350. self.cleanup_logs.setChecked(True)
  1351. self.cleanup_screenshots = QtWidgets.QCheckBox()
  1352. self.cleanup_screenshots.setText("Screenshots")
  1353. self.cleanup_screenshots.setChecked(True)
  1354. self.cleanup_downloads = QtWidgets.QCheckBox()
  1355. self.cleanup_downloads.setText("Downloads")
  1356. self.cleanup_downloads.setChecked(True)
  1357. self.cleanup_reports = QtWidgets.QCheckBox() # cleanup reports
  1358. self.cleanup_reports.setText("Reports") # cleanup reports
  1359. self.cleanup_reports.setChecked(True) # cleanup reports
  1360. hlay = QtWidgets.QHBoxLayout()
  1361. label = QtWidgets.QLabel()
  1362. label.setText("Days: ")
  1363. self.cleanup_days = QtWidgets.QLineEdit("31", self.clean_dialog)
  1364. self.cleanup_days.setValidator(QtGui.QIntValidator())
  1365. self.cleanup_days.setStyleSheet("background-color: rgb(255, 255, 255);")
  1366. hlay.addWidget(label)
  1367. hlay.addWidget(self.cleanup_days)
  1368. button = QtWidgets.QPushButton("Cleanup", self.clean_dialog)
  1369. button.setStyleSheet("color: rgb(255, 255, 255); background-color: rgb(204, 0, 0);")
  1370. self.cleanup_status = QtWidgets.QStatusBar()
  1371. vlay.addWidget(self.cleanup_logs)
  1372. vlay.addWidget(self.cleanup_screenshots)
  1373. vlay.addWidget(self.cleanup_downloads)
  1374. vlay.addWidget(self.cleanup_reports) # cleanup reports
  1375. vlay.addLayout(hlay)
  1376. vlay.addWidget(button)
  1377. vlay.addWidget(self.cleanup_status)
  1378. self.clean_dialog.setLayout(vlay)
  1379. button.clicked.connect(self.cleanup_button)
  1380. self.clean_dialog.exec_()
  1381. def cleanup_button(self):
  1382. self.cleanup_status.showMessage("Cleaning...")
  1383. text = self.cleanup_days.text()
  1384. if len(text) > 0:
  1385. days = int(text)
  1386. else:
  1387. days = 0
  1388. c = Cleanup(days)
  1389. if self.cleanup_logs.isChecked():
  1390. c.clean_logs()
  1391. if self.cleanup_screenshots.isChecked():
  1392. c.clean_screenshots()
  1393. if self.cleanup_downloads.isChecked():
  1394. c.clean_downloads()
  1395. # cleanup reports
  1396. if self.cleanup_reports.isChecked():
  1397. c.clean_reports()
  1398. self.cleanup_status.showMessage("Cleaning Complete!")
  1399. def showReport(self):
  1400. r = Dashboard()
  1401. r.show()
  1402. def testDataGenerator(self):
  1403. if len(self.ResultLengthInput.text()) > 0:
  1404. batch_size = int(self.ResultLengthInput.text())
  1405. else:
  1406. batch_size = 0
  1407. input_file = self.InputFileEdit.text()
  1408. input_file_name = os.path.basename(input_file)
  1409. tdg = TestDataGenerator(input_file, self.selectedSheet)
  1410. outputfile = os.path.join(
  1411. self.OutputFileEdit.text(),
  1412. f"{input_file_name}_{self.selectedSheet}_{utils.datetime_return()}.xlsx"
  1413. )
  1414. tdg.write(outputfile=outputfile, batch_size=batch_size)
  1415. self.TDGResult = outputfile
  1416. self.statusMessage(f"Data Generated Successfully: {outputfile}", 3000)
  1417. # Controller
  1418. class MainController:
  1419. def __init__(self):
  1420. self.widget = QtWidgets.QWidget()
  1421. self.window = QtWidgets.QMainWindow()
  1422. self.main = MainWindow()
  1423. def show_main(self):
  1424. self.main = MainWindow()
  1425. self.main.setupUi(self.window)
  1426. self.main.close_obj.connect(self.close)
  1427. self.window.show()
  1428. def close(self):
  1429. self.window.deleteLater()
  1430. self.window.destroyed.connect(self.delVars)
  1431. def delVars(self):
  1432. del self.widget
  1433. del self.main
  1434. del self.window
  1435. if __name__ == "__main__":
  1436. import sys
  1437. app = QtWidgets.QApplication(sys.argv)
  1438. controller = MainController()
  1439. controller.show_main()
  1440. appExec = app.exec_()
  1441. del controller
  1442. sys.exit(appExec)