uimain.py 62 KB

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