diff --git a/Resources/Editor/Notepad++_7_7_x32.zip b/Resources/Editor/Notepad++_7_7_x32.zip new file mode 100644 index 00000000..7c9e891d Binary files /dev/null and b/Resources/Editor/Notepad++_7_7_x32.zip differ diff --git a/Resources/Editor/Notepad++_7_7_x64.zip b/Resources/Editor/Notepad++_7_7_x64.zip new file mode 100644 index 00000000..39ca63a5 Binary files /dev/null and b/Resources/Editor/Notepad++_7_7_x64.zip differ diff --git a/Robot/GUI.py b/Robot/GUI.py index ce80b95d..4d3bd7a2 100644 --- a/Robot/GUI.py +++ b/Robot/GUI.py @@ -15,6 +15,14 @@ import traceback import ProcessCommunicator import JSONNormalize from threading import Timer +import datetime +import logging +#Создать файл логирования +# add filemode="w" to overwrite +if not os.path.exists("Reports"): + os.makedirs("Reports") +logging.basicConfig(filename="Reports\ReportRobotGUIRun_"+datetime.datetime.now().strftime("%Y_%m_%d__%H_%M_%S")+".log", level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s") + #################################### #Info: GUI module of the Robot app (OpenRPA - Robot) @@ -197,11 +205,13 @@ def UIOSelector_Get_UIO (inSpecificationList,inElement=None): def PWASpecification_Get_UIO(inControlSpecificationArray): #Определение backend lBackend=mDefaultPywinautoBackend + #pdb.set_trace() if "backend" in inControlSpecificationArray[0]: lBackend=inControlSpecificationArray[0]["backend"] inControlSpecificationArray[0].pop("backend") #Подготовка входного массива - inControlSpecificationArray=UIOSelector_SearchNormalize_UIOSelector(inControlSpecificationArray) + inControlSpecificationOriginArray=inControlSpecificationArray + inControlSpecificationArray=UIOSelector_SearchProcessNormalize_UIOSelector(inControlSpecificationArray) #Выполнить идентификацию объектов, если передан массив lResultList=[]; lTempObject=None @@ -222,7 +232,7 @@ def PWASpecification_Get_UIO(inControlSpecificationArray): #Скорректировано из-за недопонимания структуры lTempObject=lRPAApplication #Нормализация массива для целей выборки объекта (удаление конфликтующих ключей) - inControlSpecificationArray=UIOSelector_SearchNormalize_UIOSelector(inControlSpecificationArray) + inControlSpecificationArray=UIOSelector_SearchUIONormalize_UIOSelector(inControlSpecificationOriginArray) #Циклическое прохождение к недрам объекта for lWindowSpecification in inControlSpecificationArray[0:]: lTempObject=lTempObject.window(**lWindowSpecification) @@ -240,6 +250,8 @@ def UIOSelector_SearchChildByMouse_UIO(inElementSpecification): #Ветка поиска в режиме реального времени #Сбросить нажатие Ctrl, если оно было bool(win32api.GetAsyncKeyState(17)) + #Оптимизация - получить объект для опроса единажды + lUIORoot=UIOSelector_Get_UIO(inElementSpecification) lFlagLoop = True while lFlagLoop: #Проверить, нажата ли клавиша Ctrl (код 17) @@ -251,7 +263,7 @@ def UIOSelector_SearchChildByMouse_UIO(inElementSpecification): lElementFounded={} #Создать карту пикселей и элементов #####Внимание! Функция UIOXY_SearchChild_ListDict не написана - lElementFoundedList=UIOXY_SearchChild_ListDict(UIOSelector_Get_UIO(inElementSpecification),lX,lY) + lElementFoundedList=UIOXY_SearchChild_ListDict(lUIORoot,lX,lY) #print(lElementFoundedList) lElementFounded=lElementFoundedList[-1]["element"] #Подсветить объект, если он мышь раньше стояла на другом объекте @@ -274,7 +286,7 @@ def UIOSelector_SearchChildByMouse_UIO(inElementSpecification): def UIOSelector_SearchChildByMouse_UIOTree(inElementSpecification): lItemInfo = [] #Запустить функцию поиска элемента по мыши - lElementList = UIOSelector_SearchChildByMouse_UIO(inElementSpecification,inFlagIsSearchOnline) + lElementList = UIOSelector_SearchChildByMouse_UIO(inElementSpecification) lElement = lElementList[-1]['element'] #Detect backend of the elements lFlagIsBackendWin32 = True @@ -311,6 +323,8 @@ def UIOSelector_SearchChildByMouse_UIOTree(inElementSpecification): #Переход на родительский объект #lElement = lElement.parent() lListIterator=lListIterator+1 + #Добавить информацию о Backend в первый объект + lItemInfo[0]["backend"]=lElement.backend.name #Вернуть результат return lItemInfo #################################################################################################### @@ -387,7 +401,7 @@ def UIOSelector_TryRestore_Dict(inSpecificationList): lResult={} try: #Подготовка взодного массива - inControlSpecificationArray=UIOSelector_SearchNormalize_UIOSelector(inSpecificationList) + inControlSpecificationArray=UIOSelector_SearchUIONormalize_UIOSelector(inSpecificationList) #Выполнить подключение к объекту. Восстановление необходимо только в бэке win32, #так как в uia свернутое окно не распознается lRPAApplication = pywinauto.Application(backend="win32") @@ -451,7 +465,7 @@ def UIOSelectorUIOActivity_Run_Dict(inControlSpecificationArray,inActionName,inA #old name - ElementGetInfo def UIOSelector_Get_UIOInfo(inControlSpecificationArray): #Подготовка входного массива - inControlSpecificationArray=UIOSelector_SearchNormalize_UIOSelector(inControlSpecificationArray) + inControlSpecificationArray=UIOSelector_SearchUIONormalize_UIOSelector(inControlSpecificationArray) #Выполнить идентификацию объектов, если передан массив lResultList=[]; if len(inControlSpecificationArray) > 0: @@ -559,7 +573,7 @@ def UIOXY_SearchChild_ListDict(inRootElement,inX,inY,inHierarchyList=[]): #old name - ElementGetChildElementList def UIOSelector_GetChildList_UIOList(inControlSpecificationArray=[],inBackend=mDefaultPywinautoBackend): #Подготовка входного массива - inControlSpecificationArray=UIOSelector_SearchNormalize_UIOSelector(inControlSpecificationArray) + inControlSpecificationArray=UIOSelector_SearchUIONormalize_UIOSelector(inControlSpecificationArray) #Выполнить идентификацию объектов, если передан массив lResultList=[]; #ctypes.windll.user32.MessageBoxW(0, str(inControlSpecificationArray), "Your title", 1) @@ -581,6 +595,9 @@ def UIOSelector_GetChildList_UIOList(inControlSpecificationArray=[],inBackend=mD lIterator=lIterator+1 else: lResultList=BackendStr_GetTopLevelList_UIOInfo(inBackend) + #Установка бэк-енда на первый элемент + for lItem in lResultList: + lItem["backend"]=inBackend return lResultList #################################################################################################### @@ -588,7 +605,7 @@ def UIOSelector_GetChildList_UIOList(inControlSpecificationArray=[],inBackend=mD #inControlSpecificationArray - UIOSelector (can be dirty) #old name 1 - ElementSpecificationArraySearchPrepare #old name 2 - ElementSpecificationListNormalize -def UIOSelector_SearchNormalize_UIOSelector (inControlSpecificationArray): +def UIOSelector_SearchUIONormalize_UIOSelector (inControlSpecificationArray): lResult=[] #Циклический обход for lSpecificationItem in inControlSpecificationArray: @@ -641,11 +658,99 @@ def UIOSelector_SearchNormalize_UIOSelector (inControlSpecificationArray): lSpecificationItemNew.pop("control_id") if "control_type" in lSpecificationItemNew: lSpecificationItemNew.pop("control_type") + #Проверить наличие handle - если он есть, то удалить process, control_id и control_type из-за того, что они мешают друг другу + if 'handle' in lSpecificationItemNew: + if "control_id" in lSpecificationItemNew: + lSpecificationItemNew.pop("control_id") + if "control_type" in lSpecificationItemNew: + lSpecificationItemNew.pop("control_type") + if "process" in lSpecificationItemNew: + lSpecificationItemNew.pop("process") + #Иначе Проверить наличие process - если он есть, то удалить тк он нужен только при подключении к процессу + if 'process' in lSpecificationItemNew: + lSpecificationItemNew.pop("process") #Добавить строку в результирующий массив lResult.append(lSpecificationItemNew) #Вернуть результат return lResult +#################################################################################################### +#Подготовить массив для обращшения к поиску процесса (отличается от поиска элемента, тк данная функция нужна для нормализации спецификации для подключения к процессу с окнами) +#inControlSpecificationArray - UIOSelector (can be dirty) +#old name 1 - ElementSpecificationArraySearchPrepare +#old name 2 - ElementSpecificationListNormalize +def UIOSelector_SearchProcessNormalize_UIOSelector (inControlSpecificationArray): + lResult=[] + #Циклический обход + for lSpecificationItem in inControlSpecificationArray: + lSpecificationItemNew=lSpecificationItem.copy() + #Перебор всех элементов + for lItemKey,lItemValue in lSpecificationItem.items(): + #Флаг удаления атрибута + lFlagRemoveAttribute=False + ############################# + #Если является вложенным словарем - удалить + if type(lItemValue) is dict: + lFlagRemoveAttribute=True + #Является типом None + if lItemValue is None: + lFlagRemoveAttribute=True + #Проверка допустимого ключевого слова + if ( + lItemKey == "class_name" or + lItemKey == "class_name_re" or + lItemKey == "parent" or + lItemKey == "process" or + lItemKey == "title" or + lItemKey == "title_re" or + lItemKey == "top_level_only" or + lItemKey == "visible_only" or + lItemKey == "enabled_only" or + lItemKey == "best_match" or + lItemKey == "handle" or + lItemKey == "ctrl_index" or + lItemKey == "found_index" or + lItemKey == "predicate_func" or + lItemKey == "active_only" or + lItemKey == "control_id" or + lItemKey == "control_type" or + lItemKey == "auto_id" or + lItemKey == "framework_id" or + lItemKey == "backend"): + True == True + else: + lFlagRemoveAttribute=True + + ############################# + #Конструкция по удалению ключа из словаря + if lFlagRemoveAttribute: + lSpecificationItemNew.pop(lItemKey) + #Проверит наличие ctrl_index - если он есть, то удалить control_id и control_type из-за того, что они мешают друг другу + if 'ctrl_index' in lSpecificationItemNew: + if "control_id" in lSpecificationItemNew: + lSpecificationItemNew.pop("control_id") + if "control_type" in lSpecificationItemNew: + lSpecificationItemNew.pop("control_type") + #Проверить наличие handle - если он есть, то удалить process, control_id и control_type из-за того, что они мешают друг другу + if 'handle' in lSpecificationItemNew: + if "control_id" in lSpecificationItemNew: + lSpecificationItemNew.pop("control_id") + if "control_type" in lSpecificationItemNew: + lSpecificationItemNew.pop("control_type") + if "process" in lSpecificationItemNew: + lSpecificationItemNew.pop("process") + #Иначе Проверить наличие process - если он есть, то удалить title, control_id и control_type из-за того, что они мешают друг другу + elif 'process' in lSpecificationItemNew: + if "control_id" in lSpecificationItemNew: + lSpecificationItemNew.pop("control_id") + if "control_type" in lSpecificationItemNew: + lSpecificationItemNew.pop("control_type") + if "title" in lSpecificationItemNew: + lSpecificationItemNew.pop("title") + #Добавить строку в результирующий массив + lResult.append(lSpecificationItemNew) + #Вернуть результат + return lResult #################################################################################################### #Transfer UI object element info (pywinauto) to UIOInfo (dict of attributes) #inElementInfo - UIOEI @@ -806,13 +911,24 @@ def UIO_FocusHighlight(lWrapperObject,colour='green',thickness=2,fill=win32defin +#Определить разрядность процесса +lProcessBitnessStr = str(struct.calcsize("P") * 8) ############################ #Старая версия +# Определять флаг Debug, если как второй входной параметр не поступил ключ RELEASE ############################ mFlagIsDebug=True - -#run() -lText = "Bitness:" + str(struct.calcsize("P") * 8) +if (len(sys.argv)>=2): + if (sys.argv[1].upper()=="RELEASE"): + mFlagIsDebug=False +#Оповещение о выбранном режиме +if mFlagIsDebug: + logging.info("Robot/GUI: Debug mode, x"+lProcessBitnessStr) + print ("Robot/GUI: Debug mode, x"+lProcessBitnessStr) +else: + logging.info("Robot/GUI: Release mode, x"+lProcessBitnessStr) + #Нельзя делать print в release mode тк print делает вывод в PIPE поток, что нарушает последовательность взаимодействия с родительским процессом + #print ("Robot/GUI: Release mode, x"+lProcessBitnessStr) #for line in sys.stdin: # lText=lText+line; #ctypes.windll.user32.MessageBoxW(0, lText, "Your title", 1) @@ -820,11 +936,6 @@ lText = "Bitness:" + str(struct.calcsize("P") * 8) buffer = "" lJSONInputString="" -#Алгоритм включения debug режима (если передано ключевое слово debug как параметр) -#Если есть хотя бы один параметр (sys.argv[1+]) -if len(sys.argv) > 1: - if sys.argv[1] == "debug": - mFlagIsDebug=True #Выполнить чтение буфера, если не отладка библиотеки if not mFlagIsDebug: while True: diff --git a/Robot/Robot.py b/Robot/Robot.py index 86a1f3ca..da9dbe3c 100644 --- a/Robot/Robot.py +++ b/Robot/Robot.py @@ -6,6 +6,14 @@ import os import ProcessCommunicator import importlib import traceback +import logging +import sys +import datetime +#Создать файл логирования +# add filemode="w" to overwrite +if not os.path.exists("Reports"): + os.makedirs("Reports") +logging.basicConfig(filename="Reports\ReportRobotRun_"+datetime.datetime.now().strftime("%Y_%m_%d__%H_%M_%S")+".log", level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s") #################################### #Info: Main module of the Robot app (OpenRPA - Robot) @@ -51,7 +59,7 @@ import traceback #Section: Module initialization #################### #Start childprocess - GUI Module 32 bit -mProcessGUI_x32 = subprocess.Popen(['..\\Resources\\WPy32-3720\\python-3.7.2\\python.exe','..\\Robot\\GUI.py'],stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.PIPE) +mProcessGUI_x32 = subprocess.Popen(['..\\Resources\\WPy32-3720\\python-3.7.2\\python.exe','..\\Robot\\GUI.py','release'],stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.PIPE) #Start childprocess - GUI Module 64 bit - uncomment after WPy64 installation #mProcessGUI_x64 = subprocess.Popen(['..\\Resources\\WPy64-3720\\python-3.7.2\\python.exe','GUI.py'],stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.PIPE) diff --git a/Robot/RobotRun_x32.cmd b/Robot/RobotRun_x32.cmd new file mode 100644 index 00000000..191f4e14 --- /dev/null +++ b/Robot/RobotRun_x32.cmd @@ -0,0 +1,4 @@ +cd %~dp0 +copy /Y ..\Resources\WPy32-3720\python-3.7.2\python.exe ..\Resources\WPy32-3720\python-3.7.2\OpenRPARobot.exe +.\..\Resources\WPy32-3720\python-3.7.2\OpenRPARobot.exe Robot.py "release" +pause >nul \ No newline at end of file diff --git a/Studio/Studio.py b/Studio/Studio.py index 567517a6..350d5cb6 100644 --- a/Studio/Studio.py +++ b/Studio/Studio.py @@ -71,6 +71,15 @@ class testHTTPServer_RequestHandler(BaseHTTPRequestHandler): def do_POST(self): #Restart studio if self.path == '/RestartStudio': + #self.shutdown() + # Send response status code + self.send_response(200) + # Send headers + self.send_header('Content-type','application/json') + self.end_headers() + message = json.dumps({"Result":"Restart is in progress!"}) + # Write content as utf-8 data + self.wfile.write(bytes(message, "utf8")) os.execl(sys.executable,os.path.abspath(__file__),*sys.argv) sys.exit(0) #Action ObjectInspector GetObjectList @@ -158,9 +167,11 @@ def run(): httpd = HTTPServer(server_address, testHTTPServer_RequestHandler) print('running server...') httpd.serve_forever() + #Запуск адреса в браузере + os.system("explorer http://127.0.0.1:8081") #Start childprocess 32 bit -p = subprocess.Popen(['..\\Resources\\WPy32-3720\\python-3.7.2\\python.exe','winGUI.py'],stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.PIPE) +#p = subprocess.Popen(['..\\Resources\\WPy32-3720\\python-3.7.2\\python.exe','winGUI.py'],stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.PIPE) #print(ChildProcessReadWaitString(p)) run() diff --git a/Studio/Web/Index.xhtml b/Studio/Web/Index.xhtml index 23dc35aa..2a734a87 100644 --- a/Studio/Web/Index.xhtml +++ b/Studio/Web/Index.xhtml @@ -32,7 +32,22 @@ document.execCommand('copy'); document.body.removeChild(el); }; - + ///Функция перезапуска студии + mGlobal.Actions.fRestartStudioServer= function() + { + ///Загрузка данных + $.ajax({ + type: "POST", + url: 'RestartStudio', + data: '', + success: + function(lData,l2,l3) + { + var lResponseJSON=JSON.parse(lData); + }, + dataType: "text" + }); + } ///Функция клонирования объекта mGlobal.iSysClone=function(obj,lIsCloneSubProperty,lSubItemCallback) { ///Выполнить инициализацию переменной, если она не была передана @@ -74,13 +89,13 @@ $.ajax({ type: "POST", url: 'GUIAction', - data: '{"functionName":"AutomationSearchMouseElementHierarchy","argsArray":['+JSON.stringify(lSpecificationArray)+']}', + data: '{"ModuleName":"GUI", "ActivityName":"UIOSelector_SearchChildByMouse_UIOTree","ArgumentList":['+JSON.stringify(lSpecificationArray)+']}', success: function(lData,l2,l3) { var lResponseJSON=JSON.parse(lData); ///Подготовить структуру рендеринга, если у текущего объекта имееется родитель - var lStructureToRender=lResponseJSON.outputObject; + var lStructureToRender=lResponseJSON.Result; if (lSpecificationArray.length>1) { var lStructureToRenderParent=[] var lStructureToRenderLocal=lStructureToRenderParent; @@ -99,12 +114,15 @@ } } } - ///Очистить дерево - mGlobal.ElementTree.fClear(); - ///Прогрузить новое дерево - mGlobal.ElementTree.fRender(lStructureToRender); - if (lResponseJSON.hasOwnProperty("Error")) { - mGlobal.ShowModal("GUI Error",lResponseJSON.Error); + + ///Отображение ошибки + if (lResponseJSON["ErrorFlag"]) { + mGlobal.ShowModal("GUI Error",lResponseJSON.ErrorMessage+" \nTraceback: "+lResponseJSON.ErrorTraceback); + } else { + ///Очистить дерево + mGlobal.ElementTree.fClear(); + ///Прогрузить новое дерево + mGlobal.ElementTree.fRender(lStructureToRender); } }, dataType: "text" @@ -154,7 +172,7 @@ ///Генерация кода HTML var lResultString=""; var lSubItemActionOnClick=' onclick="mGlobal.TreeLoadSubTree(\''+lElementId+'\');" ' - var lSubItemActionOnRightClick=' oncontextmenu="mGlobal.ElementHighlightNew(\''+lElementId+'\');" ' + var lSubItemActionOnRightClick=' onclick="mGlobal.ElementHighlightNew(\''+lElementId+'\');" ' var lIconSelectOnClick=' onclick="mGlobal.TreeObjectInfoLoad(\''+lElementId+'\');" ' var lIconTestOnClick=' onclick="mGlobal.Test(\''+lElementId+'\');" ' var lIconUpOnClick=' onclick="mGlobal.Actions.fAutomationSearchMouseElementHierarchyRun(\''+lElementId+'\');" ' @@ -175,14 +193,14 @@ } ///Генерация кода текущего элемента lResultString+='\ -
This example shows how to use lazy loaded images, a sticky menu, and a simple text container
+Select element in the tree to see hierarchy list