From bd8ddb0b214ce97df476aa2cb80130945ff36661 Mon Sep 17 00:00:00 2001 From: Vladislav Klychkov Date: Mon, 6 Mar 2023 11:17:56 +0300 Subject: [PATCH 01/11] new browser options + PagePrint --- Sources/pyOpenRPA/Robot/UIWeb.py | 49 +++++++++++++++++++++++++++++-- Tools/Jupyter-notebooks/start.cmd | 2 +- changelog.md | 2 ++ 3 files changed, 50 insertions(+), 3 deletions(-) diff --git a/Sources/pyOpenRPA/Robot/UIWeb.py b/Sources/pyOpenRPA/Robot/UIWeb.py index 32d34f6d..da5043cd 100755 --- a/Sources/pyOpenRPA/Robot/UIWeb.py +++ b/Sources/pyOpenRPA/Robot/UIWeb.py @@ -3,6 +3,7 @@ from selenium import webdriver, common from selenium.webdriver.common.by import By import os import sys +import json from pyOpenRPA.Tools import CrossOS import time @@ -14,7 +15,7 @@ UIO_WAIT_INTERVAL_SEC_FLOAT = 1.0 gBrowser:webdriver.Chrome = None -def BrowserChromeStart(inDriverExePathStr:str = None, inChromeExePathStr:str = None, inExtensionPathList:list = None, inProfilePathStr:str=None) -> webdriver.Chrome: +def BrowserChromeStart(inDriverExePathStr:str = None, inChromeExePathStr:str = None, inExtensionPathList:list = None, inProfilePathStr:str=None, inSaveAsPDFBool = False, inSavefileDefaultDirStr = None) -> webdriver.Chrome: """L+,W+: Выполнить запуск браузера Chrome. Если вы скачали pyOpenRPA вместе с репозиторием, то будет использоваться встроенный браузер Google Chrome. Если установка pyOpenRPA производилась другим способом, то требуется указать расположение браузера Google Chrome и соответствующего WebDriver. .. code-block:: python @@ -32,6 +33,10 @@ def BrowserChromeStart(inDriverExePathStr:str = None, inChromeExePathStr:str = N :type inExtensionPathList: list, опционально :param inProfilePathStr: Путь, по которому выполнить сохранения профиля Chrome (история, куки и т.д.), по умолчанию None (профиль не сохраняется) :type inProfilePathStr: str, опционально + :param inSaveAsPDFBool: Флаг, который обеспечивает настройки окна печати вэб-страницы как "Сохранить как PDF", по умолчанию False (настройки по умолчанию) + :type inSaveAsPDFBool: bool, опционально + :param inSavefileDefaultDirStr: Путь, по которому выполнить сохранения файла (после работы с окном печать вэб-страницы браузера) (история, куки и т.д.), по умолчанию None (файл не сохраняется) + :type inSavefileDefaultDirStr: str, опционально :return: Объект браузера Google Chrome :rtype: webdriver.Chrome """ @@ -54,8 +59,31 @@ def BrowserChromeStart(inDriverExePathStr:str = None, inChromeExePathStr:str = N if CrossOS.IS_WINDOWS_BOOL: inChromeExePathStr = os.path.join(lResourcePathStr, "WChrome64-840414730", "App", "Chrome-bin", "chrome.exe") elif CrossOS.IS_LINUX_BOOL: inChromeExePathStr = os.path.join(lResourcePathStr, "LChrome64-10305060114", "data", "chrome") if inExtensionPathList == None: inExtensionPathList = [] - # Set full path to exe of the chrome + # Установка настроек окна печати, если необходимо lWebDriverChromeOptionsInstance = webdriver.ChromeOptions() + if inSaveAsPDFBool == True and inSavefileDefaultDirStr is not None: + print_settings = { + "recentDestinations": [{ + "id": "Save as PDF", + "origin": "local", + "account": "", + }], + "selectedDestinationId": "Save as PDF", + "version": 2, # в chrome - это номер варинта "сохранить как PDF" + "isHeaderFooterEnabled": False, # хедеры HTML на странице + "isLandscapeEnabled": False # ориентация (True - альбомная) + } + prefs = {'printing.print_preview_sticky_settings.appState': json.dumps(print_settings), + "download.prompt_for_download": False, + "profile.default_content_setting_values.automatic_downloads": 1, + "download.default_directory": inSavefileDefaultDirStr, + "savefile.default_directory": inSavefileDefaultDirStr, + "download.directory_upgrade": True, + "safebrowsing.enabled": True} + + lWebDriverChromeOptionsInstance.add_experimental_option('prefs', prefs) + lWebDriverChromeOptionsInstance.add_argument('--kiosk-printing') + # Set full path to exe of the chrome lWebDriverChromeOptionsInstance.binary_location = inChromeExePathStr #lWebDriverChromeOptionsInstance2 = webdriver.ChromeOptions() if inProfilePathStr is not None: @@ -107,6 +135,23 @@ def PageOpen(inURLStr: str): """ global gBrowser if gBrowser is not None: gBrowser.get(inURLStr) + +def PagePrint(): + """L+,W+: Открыть окно печати браузера. + + .. code-block:: python + + # UIWeb: Взаимодействие с ui web + from pyOpenRPA.Robot import UIWeb + import time + UIWeb.BrowserChromeStart() + UIWeb.PageOpen("https://mail.ru") + time.sleep(1) + UIWeb.PagePrint() + UIWeb.BrowserClose() + + """ + PageJSExecute(inJSStr=f"window.print()") def PageScrollTo(inVerticalPxInt=0, inHorizontalPxInt=0): """L+,W+: Выполнить прокрутку страницы (по вертикали или по горизонтали) diff --git a/Tools/Jupyter-notebooks/start.cmd b/Tools/Jupyter-notebooks/start.cmd index 6f9fbd43..84e86ddf 100755 --- a/Tools/Jupyter-notebooks/start.cmd +++ b/Tools/Jupyter-notebooks/start.cmd @@ -1,3 +1,3 @@ chcp 65001 call init-python-env.cmd jupyter-notebook.exe jupyter-notebook.exe -jupyter-notebook.exe -m notebook --notebook-dir=%~dp0 \ No newline at end of file +jupyter-notebook.exe -m notebook --notebook-dir="%~dp0 \ No newline at end of file diff --git a/changelog.md b/changelog.md index ce53e791..a8055e93 100755 --- a/changelog.md +++ b/changelog.md @@ -24,6 +24,8 @@ AGT - AGENT - - Возможность авторизации в формате login@domain - РОБОТ - - Убрали лишний print из Screen.BoxAnchorRuleCheck +- - pyOpenRPA.Robot.UIWeb.BrowserChromeStart - добавлен флаг печати в PDF и директория для сохранения при инициализации браузера +- - Добавлена новая функция - pyOpenRPA.Robot.UIWeb.PagePrint. Вызывает окно печати браузера. [1.3.1] - ОРКЕСТРАТОР From c7a14db2d49fd935fba2a9cc0366476f194daa7e Mon Sep 17 00:00:00 2001 From: Vladislav Klychkov Date: Mon, 6 Mar 2023 11:51:10 +0300 Subject: [PATCH 02/11] Rework UIOSelectorClick + new UIOSelectorSetValue (UIWeb) --- Sources/pyOpenRPA/Robot/UIWeb.py | 29 ++++++++++++++++++++++++++++- changelog.md | 2 ++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/Sources/pyOpenRPA/Robot/UIWeb.py b/Sources/pyOpenRPA/Robot/UIWeb.py index da5043cd..088a01e0 100755 --- a/Sources/pyOpenRPA/Robot/UIWeb.py +++ b/Sources/pyOpenRPA/Robot/UIWeb.py @@ -526,8 +526,35 @@ def UIOSelectorClick(inUIOSelectorStr: str): :param inUIOSelectorStr: XPATH или CSS селектор UI элемента на web странице. Подсказки по CSS: https://devhints.io/css Подсказки по XPath: https://devhints.io/xpath :type inUIOSelectorStr: str """ - PageJSExecute(inJSStr=f"document.querySelector('{inUIOSelectorStr}').click()") + if UIOSelectorDetect(inUIOSelectorStr=inUIOSelectorStr) == "CSS": + PageJSExecute(inJSStr=f"document.querySelector('{inUIOSelectorStr}').click()") + else: + PageJSExecute(inJSStr=f"document.evaluate('{inUIOSelectorStr}', document, null , XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue.click()") + +def UIOSelectorSetValue(inUIOSelectorStr: str, inValue: str): + """L+,W+: Установить значение элемента с селектором inUIOSelectorStr. + + .. code-block:: python + # UIWeb: Взаимодействие с ui web + from pyOpenRPA.Robot import UIWeb + UIWeb.BrowserChromeStart() + UIWeb.PageOpen("https://www.google.com/") + lUIOSelectorStr = "/html/body/div[1]/div[3]/form/div[1]/div[1]/div[1]/div/div[2]/input" + lValue = "pyOpenRPA" + UIWeb.UIOSelectorSetValue(inUIOSelectorStr = lUIOSelectorStr, inValue = lValue) + UIWeb.BrowserClose() + + :param inUIOSelectorStr: XPATH или CSS селектор UI элемента на web странице. Подсказки по CSS: https://devhints.io/css Подсказки по XPath: https://devhints.io/xpath + :type inUIOSelectorStr: str + :param inValue: Значение, которое необходимо установить + :type inValue: str + """ + if UIOSelectorDetect(inUIOSelectorStr=inUIOSelectorStr) == "CSS": + PageJSExecute(inJSStr=f"document.querySelector('{inUIOSelectorStr}').value='{inValue}'") + else: + PageJSExecute(inJSStr=f"document.evaluate('{inUIOSelectorStr}', document, null , XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue.value='{inValue}'") + def UIOSelectorWaitAppear(inUIOSelectorStr:str, inWaitSecFloat:float=UIO_WAIT_SEC_FLOAT, inWaitIntervalSecFloat:float = UIO_WAIT_INTERVAL_SEC_FLOAT): """L+,W+: Ожидать появление UI элемента на веб странице (блокирует выполнение потока), заданного по UIO селектору inUIOSelectorStr. Выполнять ожидание на протяжении inWaitSecFloat (по умолчанию 60 сек.). Проверка производится с интервалом inWaitIntervalSecFloat (по умолчанию 1 сек.) diff --git a/changelog.md b/changelog.md index a8055e93..b67d51d6 100755 --- a/changelog.md +++ b/changelog.md @@ -26,6 +26,8 @@ AGT - AGENT - - Убрали лишний print из Screen.BoxAnchorRuleCheck - - pyOpenRPA.Robot.UIWeb.BrowserChromeStart - добавлен флаг печати в PDF и директория для сохранения при инициализации браузера - - Добавлена новая функция - pyOpenRPA.Robot.UIWeb.PagePrint. Вызывает окно печати браузера. +- - pyOpenRPA.Robot.UIWeb.UIOSelectorClick - исправлено. Теперь работает и с XPath, и с CSS +- - Добавлена новая функция - pyOpenRPA.Robot.UIWeb.UIOSelectorSetValue. Изменение атрибута value по заданному UIOSelector элемента. [1.3.1] - ОРКЕСТРАТОР From b265823b98b0518541bcfd25a49ba1bc89007d26 Mon Sep 17 00:00:00 2001 From: Vladislav Klychkov Date: Mon, 6 Mar 2023 15:21:59 +0300 Subject: [PATCH 03/11] fix jupyter start + install autodocsumm --- Sources/setup.py | 1 + Tools/Jupyter-notebooks/start.cmd | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Sources/setup.py b/Sources/setup.py index fca6d516..7451a3b0 100755 --- a/Sources/setup.py +++ b/Sources/setup.py @@ -69,6 +69,7 @@ setup(name='pyOpenRPA', 'fastapi>=0.81.0', 'uvicorn>=0.18.3', 'python-multipart>=0.0.6' + 'autodocsumm>=0.2.10' ], extras_require={ ':sys_platform == "win32"': [ diff --git a/Tools/Jupyter-notebooks/start.cmd b/Tools/Jupyter-notebooks/start.cmd index 84e86ddf..8484e1de 100755 --- a/Tools/Jupyter-notebooks/start.cmd +++ b/Tools/Jupyter-notebooks/start.cmd @@ -1,3 +1,5 @@ chcp 65001 call init-python-env.cmd jupyter-notebook.exe jupyter-notebook.exe -jupyter-notebook.exe -m notebook --notebook-dir="%~dp0 \ No newline at end of file + +jupyter-notebook.exe -m notebook +pause>nul \ No newline at end of file From c16dde0fbbef4c87acc2d25cb389f2a9a9af10ae Mon Sep 17 00:00:00 2001 From: Vladislav Klychkov Date: Tue, 7 Mar 2023 12:12:29 +0300 Subject: [PATCH 04/11] fix UIDesktop Get_UIOList & UIWeb WebUserUACHierarchyGet --- Sources/pyOpenRPA/Orchestrator/Server.py | 4 +- .../Orchestrator/__Orchestrator__.py | 43 +++- Sources/pyOpenRPA/Robot/UIDesktop.py | 234 +++++++++--------- 3 files changed, 158 insertions(+), 123 deletions(-) diff --git a/Sources/pyOpenRPA/Orchestrator/Server.py b/Sources/pyOpenRPA/Orchestrator/Server.py index 23b1fd58..c01ff043 100755 --- a/Sources/pyOpenRPA/Orchestrator/Server.py +++ b/Sources/pyOpenRPA/Orchestrator/Server.py @@ -118,10 +118,10 @@ def IdentifyAuthorize(inRequest:Request, inResponse:Response, raise HTTPException(status_code=401, detail="Попытка авторизации не прошла успешно (неверная пара логин / пароль)", headers={}) ###################################### else: - raise HTTPException(status_code=401, detail="Попытка авторизации не прошла успешно (неполная пара логин / пароль)", headers={'Content-type':'text/html', 'WWW-Authenticate':'Basic'}) + raise HTTPException(status_code=401, detail="Попытка авторизации не прошла успешно (неполная пара логин / пароль)", headers={ 'WWW-Authenticate':'Basic'}) else: return None # Credentials are not required - return none - +#network.predictor.enabled lRouteList =[] diff --git a/Sources/pyOpenRPA/Orchestrator/__Orchestrator__.py b/Sources/pyOpenRPA/Orchestrator/__Orchestrator__.py index fba16897..85ad8f0c 100755 --- a/Sources/pyOpenRPA/Orchestrator/__Orchestrator__.py +++ b/Sources/pyOpenRPA/Orchestrator/__Orchestrator__.py @@ -623,6 +623,14 @@ def OrchestratorIsAdmin(): elif CrossOS.IS_LINUX_BOOL: return os.getuid()==0 else: return True +def OrchestratorIsCredentialsAsk(): + """L+,W+: Проверить, активирована ли авторизация при переходе к Оркестратору. + + :return: True - Активирована; False - Деактивирована + """ + inGSettings = GSettingsGet() + return inGSettings["ServerDict"]["AccessUsers"]["FlagCredentialsAsk"] + def OrchestratorIsInited() -> bool: """L+,W+: Проверить, было ли проинициализировано ядро Оркестратора @@ -1069,7 +1077,12 @@ def WebUserLoginGet(inAuthTokenStr: str=None) -> str: :return: Логин пользователя :rtype: str """ - if inAuthTokenStr is None: return None + + isCredentialAsk = OrchestratorIsCredentialsAsk() + if isCredentialAsk: + if inAuthTokenStr is None: raise ConnectionError("Не удается получить токен для авторизации") + else: + if inAuthTokenStr is None: return None inGS = GSettingsGet() # Get the global settings return inGS.get("ServerDict", {}).get("AccessUsers", {}).get("AuthTokensDict", {}).get(inAuthTokenStr, {}).get("User", None) @@ -1081,18 +1094,22 @@ def WebUserDomainGet(inAuthTokenStr: str=None) -> str: :return: Домен пользователя :rtype: str """ - - if inAuthTokenStr is None: return None + + isCredentialAsk = OrchestratorIsCredentialsAsk() + if isCredentialAsk: + if inAuthTokenStr is None: raise ConnectionError("Не удается получить токен для авторизации") + else: + if inAuthTokenStr is None: return None inGS = GSettingsGet() # Get the global settings return inGS.get("ServerDict", {}).get("AccessUsers", {}).get("AuthTokensDict", {}).get(inAuthTokenStr, {}).get("Domain", None) - def WebUserInfoGet(inAuthTokenStr=None): """L+,W+: Информация о пользователе, который отправил HTTP запрос. :param inRequest: Экземпляр HTTP request. Опционален, если сообщение фиксируется из под потока, который был инициирован запросом пользователя :return: Сведения в формате {"DomainUpperStr": "PYOPENRPA", "UserNameUpperStr": "IVAN.MASLOV"} """ + try: lResultDict = { "DomainUpperStr": WebUserDomainGet(inAuthTokenStr=inAuthTokenStr).upper(), @@ -1109,6 +1126,7 @@ def WebUserIsSuperToken(inAuthTokenStr: str=None): :type inAuthTokenStr: str, опционально :return: True - является супертокеном; False - не является супертокеном; None - авторизация не производилась """ + if inAuthTokenStr is None: return None inGSettings = GSettingsGet() # Get the global settings lIsSuperTokenBool = False @@ -1123,7 +1141,13 @@ def WebUserUACHierarchyGet(inAuthTokenStr: str=None) -> dict: :type inAuthTokenStr: str, опционально :return: UAC словарь доступа или {}, что означает полный доступ """ - if inAuthTokenStr is None: return {} + + isCredentialAsk = OrchestratorIsCredentialsAsk() + if isCredentialAsk: + if inAuthTokenStr is None: raise ConnectionError("Не удается получить токен для авторизации") + else: + if inAuthTokenStr is None: return {} + lDomainUpperStr = WebUserDomainGet(inAuthTokenStr=inAuthTokenStr).upper() lUserUpperStr = WebUserLoginGet(inAuthTokenStr=inAuthTokenStr).upper() if lUserUpperStr is None: return {} @@ -1137,7 +1161,14 @@ def WebUserUACCheck(inAuthTokenStr:str=None, inKeyList:list=None) -> bool: :return: True - доступ имеется, False - доступа нет :rtype: bool """ - if inAuthTokenStr is None: return True # Если авторизации не происходило - супердоступ + + isCredentialAsk = OrchestratorIsCredentialsAsk() + # Если авторизации не происходило - супердоступ + if isCredentialAsk: + if inAuthTokenStr is None: return False + else: + if inAuthTokenStr is None: return True + lResult = True # Init flag lRoleHierarchyDict = WebUserUACHierarchyGet(inAuthTokenStr=inAuthTokenStr) # get the Hierarchy # Try to get value from key list diff --git a/Sources/pyOpenRPA/Robot/UIDesktop.py b/Sources/pyOpenRPA/Robot/UIDesktop.py index 73142128..c02a38d9 100755 --- a/Sources/pyOpenRPA/Robot/UIDesktop.py +++ b/Sources/pyOpenRPA/Robot/UIDesktop.py @@ -128,123 +128,127 @@ def UIOSelector_Get_UIOList (inSpecificationList,inElement=None,inFlagRaiseExcep inSpecificationList=copy.deepcopy(inSpecificationList) lResultList=[] lChildrenList=[] - #Получить родительский объект если на вход ничего не поступило - if inElement is None: - #сформировать спецификацию на получение элемента - lRootElementSpecification=[inSpecificationList[0]] - lRootElementList=PWASpecification_Get_UIO(lRootElementSpecification) - for lRootItem in lRootElementList: - if lRootItem is not None: - lChildrenList.append(lRootItem.wrapper_object()) - #Елемент на вход поступил - выполнить его анализ - else: - #Получить список элементов - lElementChildrenList=inElement.children() - #Поступил index - точное добавление - if 'index' in inSpecificationList[0]: - if inSpecificationList[0]['index']1: + lFlagGoCheck=False + #Циклический обход по детям, на предмет соответствия всем условиям + for lChildrenItem in lElementChildrenList: + #Обработка глубины depth (рекурсивный вызов для всех детей с занижением индекса глубины) + #По умолчанию значение глубины 1 + if 'depth_end' in inSpecificationList[0]: + if inSpecificationList[0]['depth_end']>1: + #Подготовка новой версии спецификации + lChildrenItemNewSpecificationList=inSpecificationList.copy() + lChildrenItemNewSpecificationList[0]=lChildrenItemNewSpecificationList[0].copy() + lChildrenItemNewSpecificationList[0]["depth_end"]=lChildrenItemNewSpecificationList[0]["depth_end"]-1 + if 'depth_start' in lChildrenItemNewSpecificationList[0]: + lChildrenItemNewSpecificationList[0]["depth_start"]=lChildrenItemNewSpecificationList[0]["depth_start"]-1 + #Циклический вызов для всех детей со скорректированной спецификацией + lResultList.extend(UIOSelector_Get_UIOList(lChildrenItemNewSpecificationList,lChildrenItem,inFlagRaiseException)) + #Фильтрация + #TODO Сделать поддержку этих атрибутов для первого уровня селектора + if lFlagGoCheck: + lFlagAddChild=True + #Фильтрация по title + if 'title' in inSpecificationList[0]: + if lChildrenItem.element_info.name != inSpecificationList[0]["title"]: + lFlagAddChild=False + #Фильтрация по title_re (regexp) + if 'title_re' in inSpecificationList[0]: + if re.fullmatch(inSpecificationList[0]["title_re"],lChildrenItem.element_info.name) is None: + lFlagAddChild=False + #Фильтрация по rich_text + if 'rich_text' in inSpecificationList[0]: + if lChildrenItem.element_info.rich_text != inSpecificationList[0]["rich_text"]: + lFlagAddChild=False + #Фильтрация по rich_text_re (regexp) + if 'rich_text_re' in inSpecificationList[0]: + if re.fullmatch(inSpecificationList[0]["rich_text_re"],lChildrenItem.element_info.rich_text) is None: + lFlagAddChild=False + #Фильтрация по class_name + if 'class_name' in inSpecificationList[0]: + if lChildrenItem.element_info.class_name != inSpecificationList[0]["class_name"]: + lFlagAddChild=False + #Фильтрация по class_name_re (regexp) + if 'class_name_re' in inSpecificationList[0]: + if re.fullmatch(inSpecificationList[0]["class_name_re"],lChildrenItem.element_info.class_name) is None: + lFlagAddChild=False + #Фильтрация по friendly_class_name + if 'friendly_class_name' in inSpecificationList[0]: + if lChildrenItem.friendly_class_name() != inSpecificationList[0]["friendly_class_name"]: + lFlagAddChild=False + #Фильтрация по friendly_class_name_re (regexp) + if 'friendly_class_name_re' in inSpecificationList[0]: + if re.fullmatch(inSpecificationList[0]["friendly_class_name_re"],lChildrenItem.friendly_class_name) is None: + lFlagAddChild=False + #Фильтрация по control_type + if 'control_type' in inSpecificationList[0]: + if lChildrenItem.element_info.control_type != inSpecificationList[0]["control_type"]: + lFlagAddChild=False + #Фильтрация по control_type_re (regexp) + if 'control_type_re' in inSpecificationList[0]: + if re.fullmatch(inSpecificationList[0]["control_type_re"],lChildrenItem.element_info.control_type) is None: + lFlagAddChild=False + #Фильтрация по is_enabled (bool) + if 'is_enabled' in inSpecificationList[0]: + if lChildrenItem.is_enabled()!=inSpecificationList[0]["is_enabled"]: + lFlagAddChild=False + #Фильтрация по is_visible (bool) + if 'is_visible' in inSpecificationList[0]: + if lChildrenItem.is_visible()!=inSpecificationList[0]["is_visible"]: + lFlagAddChild=False + ##### + #Все проверки пройдены - флаг добавления + if lFlagAddChild: + lChildrenList.append(lChildrenItem) + #Выполнить рекурсивный вызов (уменьшение количества спецификаций), если спецификация больше одного элемента + #????????Зачем в условии ниже is not None ??????????? + if len(inSpecificationList)>1 and len(lChildrenList)>0: + #Вызвать рекурсивно функцию получения следующего объекта, если в спецификации есть следующий объект + for lChildElement in lChildrenList: + lResultList.extend(UIOSelector_Get_UIOList(inSpecificationList[1:],lChildElement,inFlagRaiseException)) else: - lFlagGoCheck=True - #Учесть поле depth_start (если указано) - if 'depth_start' in inSpecificationList[0]: - if inSpecificationList[0]["depth_start"]>1: - lFlagGoCheck=False - #Циклический обход по детям, на предмет соответствия всем условиям - for lChildrenItem in lElementChildrenList: - #Обработка глубины depth (рекурсивный вызов для всех детей с занижением индекса глубины) - #По умолчанию значение глубины 1 - if 'depth_end' in inSpecificationList[0]: - if inSpecificationList[0]['depth_end']>1: - #Подготовка новой версии спецификации - lChildrenItemNewSpecificationList=inSpecificationList.copy() - lChildrenItemNewSpecificationList[0]=lChildrenItemNewSpecificationList[0].copy() - lChildrenItemNewSpecificationList[0]["depth_end"]=lChildrenItemNewSpecificationList[0]["depth_end"]-1 - if 'depth_start' in lChildrenItemNewSpecificationList[0]: - lChildrenItemNewSpecificationList[0]["depth_start"]=lChildrenItemNewSpecificationList[0]["depth_start"]-1 - #Циклический вызов для всех детей со скорректированной спецификацией - lResultList.extend(UIOSelector_Get_UIOList(lChildrenItemNewSpecificationList,lChildrenItem,inFlagRaiseException)) - #Фильтрация - #TODO Сделать поддержку этих атрибутов для первого уровня селектора - if lFlagGoCheck: - lFlagAddChild=True - #Фильтрация по title - if 'title' in inSpecificationList[0]: - if lChildrenItem.element_info.name != inSpecificationList[0]["title"]: - lFlagAddChild=False - #Фильтрация по title_re (regexp) - if 'title_re' in inSpecificationList[0]: - if re.fullmatch(inSpecificationList[0]["title_re"],lChildrenItem.element_info.name) is None: - lFlagAddChild=False - #Фильтрация по rich_text - if 'rich_text' in inSpecificationList[0]: - if lChildrenItem.element_info.rich_text != inSpecificationList[0]["rich_text"]: - lFlagAddChild=False - #Фильтрация по rich_text_re (regexp) - if 'rich_text_re' in inSpecificationList[0]: - if re.fullmatch(inSpecificationList[0]["rich_text_re"],lChildrenItem.element_info.rich_text) is None: - lFlagAddChild=False - #Фильтрация по class_name - if 'class_name' in inSpecificationList[0]: - if lChildrenItem.element_info.class_name != inSpecificationList[0]["class_name"]: - lFlagAddChild=False - #Фильтрация по class_name_re (regexp) - if 'class_name_re' in inSpecificationList[0]: - if re.fullmatch(inSpecificationList[0]["class_name_re"],lChildrenItem.element_info.class_name) is None: - lFlagAddChild=False - #Фильтрация по friendly_class_name - if 'friendly_class_name' in inSpecificationList[0]: - if lChildrenItem.friendly_class_name() != inSpecificationList[0]["friendly_class_name"]: - lFlagAddChild=False - #Фильтрация по friendly_class_name_re (regexp) - if 'friendly_class_name_re' in inSpecificationList[0]: - if re.fullmatch(inSpecificationList[0]["friendly_class_name_re"],lChildrenItem.friendly_class_name) is None: - lFlagAddChild=False - #Фильтрация по control_type - if 'control_type' in inSpecificationList[0]: - if lChildrenItem.element_info.control_type != inSpecificationList[0]["control_type"]: - lFlagAddChild=False - #Фильтрация по control_type_re (regexp) - if 'control_type_re' in inSpecificationList[0]: - if re.fullmatch(inSpecificationList[0]["control_type_re"],lChildrenItem.element_info.control_type) is None: - lFlagAddChild=False - #Фильтрация по is_enabled (bool) - if 'is_enabled' in inSpecificationList[0]: - if lChildrenItem.is_enabled()!=inSpecificationList[0]["is_enabled"]: - lFlagAddChild=False - #Фильтрация по is_visible (bool) - if 'is_visible' in inSpecificationList[0]: - if lChildrenItem.is_visible()!=inSpecificationList[0]["is_visible"]: - lFlagAddChild=False - ##### - #Все проверки пройдены - флаг добавления - if lFlagAddChild: - lChildrenList.append(lChildrenItem) - #Выполнить рекурсивный вызов (уменьшение количества спецификаций), если спецификация больше одного элемента - #????????Зачем в условии ниже is not None ??????????? - if len(inSpecificationList)>1 and len(lChildrenList)>0: - #Вызвать рекурсивно функцию получения следующего объекта, если в спецификации есть следующий объект - for lChildElement in lChildrenList: - lResultList.extend(UIOSelector_Get_UIOList(inSpecificationList[1:],lChildElement,inFlagRaiseException)) - else: - lResultList.extend(lChildrenList) - #Условие, если результирующий список пустой и установлен флаг создания ошибки (и inElement is None - не следствие рекурсивного вызова) - if inElement is None and len(lResultList)==0 and inFlagRaiseException: - raise pywinauto.findwindows.ElementNotFoundError("Robot can't find element by the UIOSelector") - return lResultList + lResultList.extend(lChildrenList) + #Условие, если результирующий список пустой и установлен флаг создания ошибки (и inElement is None - не следствие рекурсивного вызова) + if inElement is None and len(lResultList)==0 and inFlagRaiseException: + raise pywinauto.findwindows.ElementNotFoundError("Robot can't find element by the UIOSelector") + return lResultList + except Exception: + if inFlagRaiseException: raise ValueError("Не удается получить UIOList") + else: return [] #old:PywinautoExtElementGet def UIOSelector_Get_UIO (inSpecificationList,inElement=None,inFlagRaiseException=True): From 7f01b9c66e1a79fc943f14da06764765236291b2 Mon Sep 17 00:00:00 2001 From: Vladislav Klychkov Date: Thu, 9 Mar 2023 13:06:24 +0300 Subject: [PATCH 05/11] changelog after fix issues --- changelog.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/changelog.md b/changelog.md index b67d51d6..14edc6dc 100755 --- a/changelog.md +++ b/changelog.md @@ -11,6 +11,8 @@ AGT - AGENT ****************************** - ОБЩЕЕ - - Utils: Disk - подготовка файлов / папок, если они не обнаружены (полезно при первом запуске, если требуются БД/ Файлы хранилищ) +- - Jupyter: запуск из директорий, пути к которым содержат пробелы +- - PyOpenRPA: autodocsumm - добавлено к пакетам. - ОРКЕСТРАТОР - - Исправление совместимости URL путей с некорыми ресурсами для отработки в LINUX - - Поддержка многотысячной аудитории, одновременно работающей в панели управления (async server-data server-log with fastapi) @@ -22,12 +24,15 @@ AGT - AGENT - - AgentProcessList исправление (hotfix) - - Права доступа в случае незаявленного пользователя (Hotfix) - - Возможность авторизации в формате login@domain +- - Переработана система обработки AuthTokenStr==None. Теперь поднимается exception в случаях, когда авторизация производилась, но AuthTokenStr==None +- - Добавлена новая функция - Orchestrator.OrchestratorIsCredentialsAsk. Определяет была ли произведена авторизация пользователя - РОБОТ - - Убрали лишний print из Screen.BoxAnchorRuleCheck - - pyOpenRPA.Robot.UIWeb.BrowserChromeStart - добавлен флаг печати в PDF и директория для сохранения при инициализации браузера -- - Добавлена новая функция - pyOpenRPA.Robot.UIWeb.PagePrint. Вызывает окно печати браузера. +- - Добавлена новая функция - pyOpenRPA.Robot.UIWeb.PagePrint. Вызывает окно печати браузера - - pyOpenRPA.Robot.UIWeb.UIOSelectorClick - исправлено. Теперь работает и с XPath, и с CSS -- - Добавлена новая функция - pyOpenRPA.Robot.UIWeb.UIOSelectorSetValue. Изменение атрибута value по заданному UIOSelector элемента. +- - Добавлена новая функция - pyOpenRPA.Robot.UIWeb.UIOSelectorSetValue. Изменение атрибута value по заданному UIOSelector элемента +- - pyOpenRPA.Robot.UIDesktop.UIOSelector_Get_UIOList - Исправлена обработка exceptions. Теперь флаг ловит все исключения [1.3.1] - ОРКЕСТРАТОР From aaefa6ab2af7a75fd3d1644c29dab0dab01eda00 Mon Sep 17 00:00:00 2001 From: Vladislav Klychkov Date: Mon, 13 Mar 2023 08:51:08 +0300 Subject: [PATCH 06/11] new bad auth web page --- Sources/pyOpenRPA/Orchestrator/Server.py | 135 +++++++++++------- .../pyOpenRPA/Orchestrator/ServerSettings.py | 2 +- .../Resources/Web/orpa/badAuth.xhtml | 101 +++++++++++++ 3 files changed, 188 insertions(+), 50 deletions(-) create mode 100644 Sources/pyOpenRPA/Resources/Web/orpa/badAuth.xhtml diff --git a/Sources/pyOpenRPA/Orchestrator/Server.py b/Sources/pyOpenRPA/Orchestrator/Server.py index c01ff043..29fe84a3 100755 --- a/Sources/pyOpenRPA/Orchestrator/Server.py +++ b/Sources/pyOpenRPA/Orchestrator/Server.py @@ -18,9 +18,10 @@ from . import ServerBC # объявление import from fastapi import FastAPI, Form, Request, HTTPException, Depends, Header, Response, Body -from fastapi.responses import PlainTextResponse, HTMLResponse, FileResponse +from fastapi.responses import PlainTextResponse, HTMLResponse, FileResponse, RedirectResponse from fastapi.staticfiles import StaticFiles from fastapi.templating import Jinja2Templates +from starlette.datastructures import MutableHeaders from pydantic import BaseModel import uvicorn import io @@ -28,6 +29,7 @@ from starlette.responses import StreamingResponse from typing import Union from pyOpenRPA import __version__ +import requests import base64 import uuid import datetime @@ -43,6 +45,8 @@ app = FastAPI( swagger_ui_oauth2_redirect_url = "/orpa/fastapi/docs/oauth2-redirect", ) + + def IdentifyAuthorize(inRequest:Request, inResponse:Response, inCookiesStr: Union[str, None] = Header(default=None,alias="Cookie"), inAuthorizationStr: Union[str, None] = Header(default="",alias="Authorization")): @@ -71,59 +75,63 @@ def IdentifyAuthorize(inRequest:Request, inResponse:Response, ###################################### #Way 2 - try to logon if len(lHeaderAuthorization) == 2: - llHeaderAuthorizationDecodedUserPasswordList = base64.b64decode(lHeaderAuthorization[1]).decode("utf-8").split( - ":") - lUser = llHeaderAuthorizationDecodedUserPasswordList[0] - lPassword = llHeaderAuthorizationDecodedUserPasswordList[1] - lDomain = "" - if "\\" in lUser: - lDomain = lUser.split("\\")[0] - lUser = lUser.split("\\")[1] - elif "@" in lUser: - lDomain = lUser.split("@")[1] - lUser = lUser.split("@")[0] - lLogonBool = __Orchestrator__.OSCredentialsVerify(inUserStr=lUser, inPasswordStr=lPassword, inDomainStr=lDomain) - #Check result - if lLogonBool: # check user in gsettings rules - lLogonBool = False - gSettings = __Orchestrator__.GSettingsGet() # Set the global settings - lUserTurple = (lDomain.upper(),lUser.upper()) # Create turple key for inGSettings["ServerDict"]["AccessUsers"]["RuleDomainUserDict"] - lUserTurple2 = ("",lUser.upper()) # Create turple key for inGSettings["ServerDict"]["AccessUsers"]["RuleDomainUserDict"] - if lUserTurple in gSettings.get("ServerDict",{}).get("AccessUsers", {}).get("RuleDomainUserDict", {}): lLogonBool = True - elif lUserTurple2 in gSettings.get("ServerDict",{}).get("AccessUsers", {}).get("RuleDomainUserDict", {}): lLogonBool = True - if lLogonBool: # If user exists in UAC Dict - lResult["Domain"] = lDomain - lResult["User"] = lUser - #Create token - lAuthToken=str(uuid.uuid1()) - __Orchestrator__.GSettingsGet()["ServerDict"]["AccessUsers"]["AuthTokensDict"][lAuthToken] = {} - __Orchestrator__.GSettingsGet()["ServerDict"]["AccessUsers"]["AuthTokensDict"][lAuthToken]["Domain"] = lResult["Domain"] - __Orchestrator__.GSettingsGet()["ServerDict"]["AccessUsers"]["AuthTokensDict"][lAuthToken]["User"] = lResult["User"] - __Orchestrator__.GSettingsGet()["ServerDict"]["AccessUsers"]["AuthTokensDict"][lAuthToken]["FlagDoNotExpire"] = False - __Orchestrator__.GSettingsGet()["ServerDict"]["AccessUsers"]["AuthTokensDict"][lAuthToken]["TokenDatetime"] = datetime.datetime.now() - #Set-cookie - inResponse.set_cookie(key="AuthToken",value=lAuthToken) - mOpenRPA={} - mOpenRPA["AuthToken"] = lAuthToken - mOpenRPA["Domain"] = lResult["Domain"] - mOpenRPA["User"] = lResult["User"] - mOpenRPA["IsSuperToken"] = __Orchestrator__.GSettingsGet().get("ServerDict", {}).get("AccessUsers", {}).get("AuthTokensDict", {}).get(mOpenRPA["AuthToken"], {}).get("FlagDoNotExpire", False) - return lAuthToken - #inRequest.OpenRPASetCookie = {} - #New engine of server - #inRequest.OpenRPAResponseDict["SetCookies"]["AuthToken"] = lAuthToken - else: - raise HTTPException(status_code=401, detail="Попытка авторизации не прошла успешно (для пользователя не заявлен доступ к оркестратору pyOpenRPA. Обратитесь в техническую поддержку)", headers={}) + if "AuthExc" in lCookies: + raise AuthException() else: - raise HTTPException(status_code=401, detail="Попытка авторизации не прошла успешно (неверная пара логин / пароль)", headers={}) - ###################################### + llHeaderAuthorizationDecodedUserPasswordList = base64.b64decode(lHeaderAuthorization[1]).decode("utf-8").split(":") + lUser = llHeaderAuthorizationDecodedUserPasswordList[0] + lPassword = llHeaderAuthorizationDecodedUserPasswordList[1] + lDomain = "" + if "\\" in lUser: + lDomain = lUser.split("\\")[0] + lUser = lUser.split("\\")[1] + elif "@" in lUser: + lDomain = lUser.split("@")[1] + lUser = lUser.split("@")[0] + lLogonBool = __Orchestrator__.OSCredentialsVerify(inUserStr=lUser, inPasswordStr=lPassword, inDomainStr=lDomain) + #Check result + if lLogonBool: # check user in gsettings rules + lLogonBool = False + gSettings = __Orchestrator__.GSettingsGet() # Set the global settings + lUserTurple = (lDomain.upper(),lUser.upper()) # Create turple key for inGSettings["ServerDict"]["AccessUsers"]["RuleDomainUserDict"] + lUserTurple2 = ("",lUser.upper()) # Create turple key for inGSettings["ServerDict"]["AccessUsers"]["RuleDomainUserDict"] + if lUserTurple in gSettings.get("ServerDict",{}).get("AccessUsers", {}).get("RuleDomainUserDict", {}): lLogonBool = True + elif lUserTurple2 in gSettings.get("ServerDict",{}).get("AccessUsers", {}).get("RuleDomainUserDict", {}): lLogonBool = True + if lLogonBool: # If user exists in UAC Dict + lResult["Domain"] = lDomain + lResult["User"] = lUser + #Create token + lAuthToken=str(uuid.uuid1()) + __Orchestrator__.GSettingsGet()["ServerDict"]["AccessUsers"]["AuthTokensDict"][lAuthToken] = {} + __Orchestrator__.GSettingsGet()["ServerDict"]["AccessUsers"]["AuthTokensDict"][lAuthToken]["Domain"] = lResult["Domain"] + __Orchestrator__.GSettingsGet()["ServerDict"]["AccessUsers"]["AuthTokensDict"][lAuthToken]["User"] = lResult["User"] + __Orchestrator__.GSettingsGet()["ServerDict"]["AccessUsers"]["AuthTokensDict"][lAuthToken]["FlagDoNotExpire"] = False + __Orchestrator__.GSettingsGet()["ServerDict"]["AccessUsers"]["AuthTokensDict"][lAuthToken]["TokenDatetime"] = datetime.datetime.now() + #Set-cookie + inResponse.set_cookie(key="AuthToken",value=lAuthToken) + mOpenRPA={} + mOpenRPA["AuthToken"] = lAuthToken + mOpenRPA["Domain"] = lResult["Domain"] + mOpenRPA["User"] = lResult["User"] + mOpenRPA["IsSuperToken"] = __Orchestrator__.GSettingsGet().get("ServerDict", {}).get("AccessUsers", {}).get("AuthTokensDict", {}).get(mOpenRPA["AuthToken"], {}).get("FlagDoNotExpire", False) + try:inResponse.delete_cookie(key="AuthExc") + except Exception:pass + return lAuthToken + #inRequest.OpenRPASetCookie = {} + #New engine of server + #inRequest.OpenRPAResponseDict["SetCookies"]["AuthToken"] = lAuthToken + else: + errorMsg = "Попытка авторизации не прошла успешно (для пользователя не заявлен доступ к оркестратору pyOpenRPA. Обратитесь в техническую поддержку)" + raise ErrorException(text=errorMsg) + else: + errorMsg = "Попытка авторизации не прошла успешно (неверная пара логин / пароль)" + raise ErrorException(text=errorMsg) else: - raise HTTPException(status_code=401, detail="Попытка авторизации не прошла успешно (неполная пара логин / пароль)", headers={ 'WWW-Authenticate':'Basic'}) + raise AuthException() else: return None # Credentials are not required - return none -#network.predictor.enabled - +# Перевод встроенных fastapi функций на авторизацию lRouteList =[] for lItem in app.router.routes: lRouteList.append(lItem) @@ -137,6 +145,35 @@ for lItem in lRouteList: tags=["FastAPI"] ) +class ErrorException(Exception): + def __init__(self, text :str, name: str="AuthExc"): + self.name = name + self.text = text + +class AuthException(Exception): + def __init__(self, name: str="AuthTryWindowCreate"): + self.name = name + + +templates = Jinja2Templates(directory=CrossOS.PathJoinList(CrossOS.PathSplitList(__file__)[:-2] + ["Resources","Web","orpa"])) + +# Обработчик ошибки авторизации (вывод информации о причинах неудачной авторизации) +@app.exception_handler(ErrorException) +async def unicorn_exception_handler(request: Request, exc:ErrorException): + response = templates.TemplateResponse(status_code=401,name="badAuth.xhtml", context={"request":request, "errorMsg":exc.text, "title":"ОРКЕСТРАТОР PYOPENRPA", "subtitle":"ПАНЕЛЬ УПРАВЛЕНИЯ", "version":__version__}) + response.set_cookie(key=exc.name,value="True") + return response + +# Обработчик попытки авторизации (отвечает за вызов окна для ввода пары логин / пароль) +@app.exception_handler(AuthException) +async def unicorn_exception_handler_2(request: Request, exc: AuthException): + response = HTMLResponse(status_code=401, headers={'Content-type':'text/html; charset=utf-8', 'WWW-Authenticate':'Basic'}) + try:response.delete_cookie(key="AuthExc") + except Exception:pass + return response + + + from . import ServerSettings diff --git a/Sources/pyOpenRPA/Orchestrator/ServerSettings.py b/Sources/pyOpenRPA/Orchestrator/ServerSettings.py index c74f8820..cda4106d 100755 --- a/Sources/pyOpenRPA/Orchestrator/ServerSettings.py +++ b/Sources/pyOpenRPA/Orchestrator/ServerSettings.py @@ -498,7 +498,7 @@ def SettingsUpdate(): #} #Orchestrator basic dependencies # Index page in server.py because of special settings {"Method":"GET", "URL": gSettingsDict["ServerDict"]["URLIndexStr"],"MatchType": "EqualNoParam", "ResponseDefRequestGlobal": pyOpenRPA_Index}, - {"Method":"GET", "URL": "/metadata.json", "MatchType": "EqualCase", "ResponseFilePath": os.path.join(lOrchestratorFolder, CrossOS.PathStr("..\\Resources\\Web\\orpa\\metadata.json")), "ResponseContentType": "application/json"}, + {"Method":"GET", "URL": "/metadata.json", "MatchType": "EqualCase", "ResponseFilePath": os.path.join(lOrchestratorFolder, CrossOS.PathStr("..\\Resources\\Web\\orpa\\metadata.json")), "ResponseContentType": "application/json","UACBool":False,}, #{"Method":"GET", "URL": "/Index.js", "MatchType": "EqualCase", "ResponseFilePath": os.path.join(lOrchestratorFolder, "Web\\Index.js"), "ResponseContentType": "text/javascript"}, {"Method":"GET", "URL": "/orpa/resources/", "MatchType": "BeginWith", "ResponseFolderPath": os.path.join(lOrchestratorFolder, CrossOS.PathStr("..\\Resources")),"UACBool":False, "UseCacheBool": True}, {"Method":"GET", "URL": "/orpa/client/resources/", "MatchType": "BeginWith", "ResponseFolderPath": os.path.join(lOrchestratorFolder, "Web"),"UACBool":False, "UseCacheBool": True}, diff --git a/Sources/pyOpenRPA/Resources/Web/orpa/badAuth.xhtml b/Sources/pyOpenRPA/Resources/Web/orpa/badAuth.xhtml new file mode 100644 index 00000000..7e8c0992 --- /dev/null +++ b/Sources/pyOpenRPA/Resources/Web/orpa/badAuth.xhtml @@ -0,0 +1,101 @@ + + + + + + + + + + + Оркестратор pyOpenRPA + + + + + + + + + + + + + + + {% include 'header.xhtml' %} +




+
+
+
Внимание
+

{{errorMsg}}

+
+ +
+ {% include 'footer.xhtml' %} + + + + + + From dde8a29117ad46cbb26cbb6da18149f856099cb0 Mon Sep 17 00:00:00 2001 From: Vladislav Klychkov Date: Mon, 13 Mar 2023 09:32:12 +0300 Subject: [PATCH 07/11] WebURLConnectFolder fix --- Sources/pyOpenRPA/Orchestrator/Server.py | 12 ++++++++---- Sources/pyOpenRPA/Orchestrator/__Orchestrator__.py | 5 +++-- changelog.md | 5 ++++- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/Sources/pyOpenRPA/Orchestrator/Server.py b/Sources/pyOpenRPA/Orchestrator/Server.py index 29fe84a3..3f877ad3 100755 --- a/Sources/pyOpenRPA/Orchestrator/Server.py +++ b/Sources/pyOpenRPA/Orchestrator/Server.py @@ -222,13 +222,17 @@ def InitFastAPI(): ServerSettings.SettingsUpdate() BCURLUpdate() -def BCURLUpdate(): +def BCURLUpdate(inExceptionFlagBool:bool=True): for lConnectItemDict in __Orchestrator__.GSettingsGet()["ServerDict"]["URLList"]: if "BCBool" not in lConnectItemDict: if "ResponseFolderPath" in lConnectItemDict: - app.mount(lConnectItemDict["URL"], - StaticFiles(directory=CrossOS.PathStr(lConnectItemDict["ResponseFolderPath"])), - name=lConnectItemDict["URL"].replace('/',"_")) + try: + app.mount(lConnectItemDict["URL"], + StaticFiles(directory=CrossOS.PathStr(lConnectItemDict["ResponseFolderPath"])), + name=lConnectItemDict["URL"].replace('/',"_")) + except: + if inExceptionFlagBool: raise RuntimeError("Fatal error. Bad FolderPath") + else: pass else: if lConnectItemDict.get("MatchType") in ["BeginWith", "EqualCase", "Equal","EqualNoParam"]: if lConnectItemDict.get("UACBool",True): diff --git a/Sources/pyOpenRPA/Orchestrator/__Orchestrator__.py b/Sources/pyOpenRPA/Orchestrator/__Orchestrator__.py index 85ad8f0c..02029da6 100755 --- a/Sources/pyOpenRPA/Orchestrator/__Orchestrator__.py +++ b/Sources/pyOpenRPA/Orchestrator/__Orchestrator__.py @@ -1223,13 +1223,14 @@ def WebURLConnectDef(inMethodStr, inURLStr, inMatchTypeStr, inDef, inContentType Server.BCURLUpdate() -def WebURLConnectFolder(inMethodStr, inURLStr, inMatchTypeStr, inFolderPathStr, inGSettings = None, inUACBool = None, inUseCacheBool= False): +def WebURLConnectFolder(inMethodStr, inURLStr, inMatchTypeStr, inFolderPathStr, inExceptionFlagBool=False, inGSettings = None, inUACBool = None, inUseCacheBool= False): """L+,W+: Подключить папку к URL. :param inMethodStr: Метод доступа по URL "GET" || "POST" :param inURLStr: URL адрес. Пример "/index" :param inMatchTypeStr: Тип соответсвия строки URL с inURLStr: "BeginWith" || "Contains" || "Equal" || "EqualCase" || "EqualNoParam" :param inFolderPathStr: Путь к папке на диске, в которой искать файл и возвращать пользователю по HTTP + :param inExceptionFlagBool: Флаг на обработку ошибки. True - показывать ошибку в терминале (остановка инициализации), False - не показывать :param inUACBool: True - Выполнять проверку прав доступа пользователя перед отправкой ответа; False - не выполнять проверку прав доступа пользователя :param inUseCacheBool: True - выполнить кэширование страницы, чтобы в следющих запросах открыть быстрее; False - не кэшировать :param inGSettings: Глобальный словарь настроек Оркестратора (синглтон) @@ -1251,7 +1252,7 @@ def WebURLConnectFolder(inMethodStr, inURLStr, inMatchTypeStr, inFolderPathStr, "UseCacheBool": inUseCacheBool } inGSettings["ServerDict"]["URLList"].append(lURLItemDict) - Server.BCURLUpdate() + Server.BCURLUpdate(inExceptionFlagBool) def WebURLConnectFile(inMethodStr, inURLStr, inMatchTypeStr, inFilePathStr, inContentTypeStr=None, inGSettings = None, inUACBool = None, inUseCacheBool = False): diff --git a/changelog.md b/changelog.md index 14edc6dc..67acea22 100755 --- a/changelog.md +++ b/changelog.md @@ -13,6 +13,7 @@ AGT - AGENT - - Utils: Disk - подготовка файлов / папок, если они не обнаружены (полезно при первом запуске, если требуются БД/ Файлы хранилищ) - - Jupyter: запуск из директорий, пути к которым содержат пробелы - - PyOpenRPA: autodocsumm - добавлено к пакетам. +- - Исправлено неправильное поведение оркестратора при авторизации в браузере Firefox. - ОРКЕСТРАТОР - - Исправление совместимости URL путей с некорыми ресурсами для отработки в LINUX - - Поддержка многотысячной аудитории, одновременно работающей в панели управления (async server-data server-log with fastapi) @@ -26,13 +27,15 @@ AGT - AGENT - - Возможность авторизации в формате login@domain - - Переработана система обработки AuthTokenStr==None. Теперь поднимается exception в случаях, когда авторизация производилась, но AuthTokenStr==None - - Добавлена новая функция - Orchestrator.OrchestratorIsCredentialsAsk. Определяет была ли произведена авторизация пользователя +- - WebURLConnectFolder - переработано. Добавлен флаг обработки ошибки, позволяющий продолжить инициализацию оркестратора, если папки не существует +- - Переработана система авторизации. Добавлена форма, появляющаяся при неудачной попытке авторизации. - РОБОТ - - Убрали лишний print из Screen.BoxAnchorRuleCheck - - pyOpenRPA.Robot.UIWeb.BrowserChromeStart - добавлен флаг печати в PDF и директория для сохранения при инициализации браузера - - Добавлена новая функция - pyOpenRPA.Robot.UIWeb.PagePrint. Вызывает окно печати браузера - - pyOpenRPA.Robot.UIWeb.UIOSelectorClick - исправлено. Теперь работает и с XPath, и с CSS - - Добавлена новая функция - pyOpenRPA.Robot.UIWeb.UIOSelectorSetValue. Изменение атрибута value по заданному UIOSelector элемента -- - pyOpenRPA.Robot.UIDesktop.UIOSelector_Get_UIOList - Исправлена обработка exceptions. Теперь флаг ловит все исключения +- - pyOpenRPA.Robot.UIDesktop.UIOSelector_Get_UIOList - исправлена обработка exceptions. Теперь флаг ловит все исключения [1.3.1] - ОРКЕСТРАТОР From 6a9de8222d205fa288d32b6b7b7a7dcd4fda30bd Mon Sep 17 00:00:00 2001 From: Vladislav Klychkov Date: Mon, 13 Mar 2023 13:17:39 +0300 Subject: [PATCH 08/11] + inFlagRaiseException in UIDesktop + studio test --- Sources/pyOpenRPA/Robot/UIDesktop.py | 61 +++++++++++++++++----------- Tools/Jupyter-notebooks/start.cmd | 4 +- changelog.md | 1 + 3 files changed, 40 insertions(+), 26 deletions(-) diff --git a/Sources/pyOpenRPA/Robot/UIDesktop.py b/Sources/pyOpenRPA/Robot/UIDesktop.py index c02a38d9..dcaa70b6 100755 --- a/Sources/pyOpenRPA/Robot/UIDesktop.py +++ b/Sources/pyOpenRPA/Robot/UIDesktop.py @@ -120,7 +120,7 @@ def UIOSelector_Get_UIOList (inSpecificationList,inElement=None,inFlagRaiseExcep :type inSpecificationList: list, обязательный :param inElement: Родительский элемент, от которого выполнить поиск UIO объектов по заданному UIO селектору. Если аргумент не задан, платформа выполнит поиск UIO объектов среди всех доступных приложений windows, которые запущены на текущей сессии :type inElement: UIO объект, опциональный - :param inFlagRaiseException: True - формировать ошибку exception, если платформа не обнаружина ни одного UIO объекта по заданному UIO селектору. False - обратный случай. По умолчанию True + :param inFlagRaiseException: True - формировать ошибку exception, если платформа не обнаружина ни одного UIO объекта по заданному UIO селектору. False - обратный случай (может привести к ошибочным результатам). По умолчанию True. :type inFlagRaiseException: bool, опциональный :return: Список UIO объектов, которые удовлетворяют условиям UIO селектора ''' @@ -246,8 +246,8 @@ def UIOSelector_Get_UIOList (inSpecificationList,inElement=None,inFlagRaiseExcep if inElement is None and len(lResultList)==0 and inFlagRaiseException: raise pywinauto.findwindows.ElementNotFoundError("Robot can't find element by the UIOSelector") return lResultList - except Exception: - if inFlagRaiseException: raise ValueError("Не удается получить UIOList") + except Exception as e: + if inFlagRaiseException: raise e else: return [] #old:PywinautoExtElementGet @@ -272,16 +272,13 @@ def UIOSelector_Get_UIO (inSpecificationList,inElement=None,inFlagRaiseException ''' lResult=None #Получить родительский объект если на вход ничего не поступило - lResultList=UIOSelector_Get_UIOList(inSpecificationList,inElement,False) + lResultList=UIOSelector_Get_UIOList(inSpecificationList,inElement,inFlagRaiseException) if len(lResultList)>0: lResult=lResultList[0] - #Условие, если результирующий список пустой и установлен флаг создания ошибки (и inElement is None - не следствие рекурсивного вызова) - if lResult is None and inFlagRaiseException: - raise pywinauto.findwindows.ElementNotFoundError("Robot can't find element by the UIOSelector") return lResult #old:- -def UIOSelector_Exist_Bool (inUIOSelector): +def UIOSelector_Exist_Bool (inUIOSelector, inFlagRaiseException=True): '''L-,W+: Проверить существование хотя бы 1-го UIO объекта по заданному UIO селектору !ВНИМАНИЕ! ДАННАЯ ФУНКЦИОНАЛЬНОСТЬ В АВТОМАТИЧЕСКОМ РЕЖИМЕ ПОДДЕРЖИВАЕТ ВСЕ РАЗРЯДНОСТИ ПРИЛОЖЕНИЙ (32|64), КОТОРЫЕ ЗАПУЩЕНЫ В СЕСИИ. PYTHON x64 ИМЕЕТ ВОЗМОЖНОСТЬ ВЗЗАИМОДЕЙСТВИЯ С x32 UIO ОБЪЕКТАМИ, НО МЫ РЕКОМЕНДУЕМ ДОПОЛНИТЕЛЬНО ИСПОЛЬЗОВАТЬ ИНТЕРПРЕТАТОР PYTHON x32 (ПОДРОБНЕЕ СМ. ФУНКЦИЮ Configure()) @@ -296,6 +293,8 @@ def UIOSelector_Exist_Bool (inUIOSelector): :param inUIOSelector: UIO Селектор, который определяет критерии поиска UIO объектов :type inUIOSelector: list, обязательный + :param inFlagRaiseException: True - формировать ошибку exception, если платформа не обнаружина ни одного UIO объекта по заданному UIO селектору. False - обратный случай (может привести к ошибочным результатам). По умолчанию True. + :type inFlagRaiseException: bool, опциональный :return: True - существует хотя бы 1 UIO объект. False - не существует ни одного UIO объекта по заданному UIO селектору ''' lResult=False @@ -303,7 +302,9 @@ def UIOSelector_Exist_Bool (inUIOSelector): lSafeOtherProcess = UIOSelector_SafeOtherGet_Process(inUIOSelector) if lSafeOtherProcess is None: #Получить родительский объект если на вход ничего не поступило - lResultList=UIOSelector_Get_UIOList(inUIOSelector, None, False) + try: + lResultList=UIOSelector_Get_UIOList(inUIOSelector, None, inFlagRaiseException) + except pywinauto.findwindows.ElementNotFoundError: return False if len(lResultList)>0: lResult=True else: @@ -323,7 +324,7 @@ def UIOSelector_Exist_Bool (inUIOSelector): return lResult #old: - -def UIOSelectorsSecs_WaitAppear_List (inSpecificationListList,inWaitSecs=86400.0,inFlagWaitAllInMoment=False): +def UIOSelectorsSecs_WaitAppear_List (inSpecificationListList,inWaitSecs=86400.0,inFlagWaitAllInMoment=False, inFlagRaiseException=True): '''L-,W+: Ожидать появление хотя бы 1-го / всех UIO объектов по заданным UIO селекторам !ВНИМАНИЕ! ДАННАЯ ФУНКЦИОНАЛЬНОСТЬ В АВТОМАТИЧЕСКОМ РЕЖИМЕ ПОДДЕРЖИВАЕТ ВСЕ РАЗРЯДНОСТИ ПРИЛОЖЕНИЙ (32|64), КОТОРЫЕ ЗАПУЩЕНЫ В СЕСИИ. PYTHON x64 ИМЕЕТ ВОЗМОЖНОСТЬ ВЗЗАИМОДЕЙСТВИЯ С x32 UIO ОБЪЕКТАМИ, НО МЫ РЕКОМЕНДУЕМ ДОПОЛНИТЕЛЬНО ИСПОЛЬЗОВАТЬ ИНТЕРПРЕТАТОР PYTHON x32 (ПОДРОБНЕЕ СМ. ФУНКЦИЮ Configure()) @@ -346,6 +347,8 @@ def UIOSelectorsSecs_WaitAppear_List (inSpecificationListList,inWaitSecs=86400.0 :type inSpecificationListList: list, обязательный :param inWaitSecs: Количество секунд, которые отвести на ожидание UIO объектов. По умолчанию 24 часа (86400 секунд) :type inWaitSecs: float, необязательный + :param inFlagRaiseException: True - формировать ошибку exception, если платформа не обнаружина ни одного UIO объекта по заданному UIO селектору. False - обратный случай (может привести к ошибочным результатам). По умолчанию True. + :type inFlagRaiseException: bool, опциональный :param inFlagWaitAllInMoment: True - Ожидать до того момента, пока не появятся все запрашиваемые UIO объекты на рабочей области :return: Список индексов, которые указывают на номер входящих UIO селекторов, которые были обнаружены на рабочей области. Пример: [0,2] ''' @@ -360,7 +363,7 @@ def UIOSelectorsSecs_WaitAppear_List (inSpecificationListList,inWaitSecs=86400.0 #Итерация проверки lIndex = 0 for lItem in inSpecificationListList: - lItemResultFlag=UIOSelector_Exist_Bool(lItem) + lItemResultFlag=UIOSelector_Exist_Bool(lItem, inFlagRaiseException=True) #Если обнаружен элемент - добавить его индекс в массив if lItemResultFlag: lResultList.append(lIndex) @@ -380,7 +383,7 @@ def UIOSelectorsSecs_WaitAppear_List (inSpecificationListList,inWaitSecs=86400.0 return lResultList #old: - -def UIOSelectorsSecs_WaitDisappear_List (inSpecificationListList,inWaitSecs=86400.0,inFlagWaitAllInMoment=False): +def UIOSelectorsSecs_WaitDisappear_List (inSpecificationListList,inWaitSecs=86400.0,inFlagWaitAllInMoment=False, inFlagRaiseException=True): '''L-,W+: Ожидать исчезновение хотя бы 1-го / всех UIO объектов по заданным UIO селекторам !ВНИМАНИЕ! ДАННАЯ ФУНКЦИОНАЛЬНОСТЬ В АВТОМАТИЧЕСКОМ РЕЖИМЕ ПОДДЕРЖИВАЕТ ВСЕ РАЗРЯДНОСТИ ПРИЛОЖЕНИЙ (32|64), КОТОРЫЕ ЗАПУЩЕНЫ В СЕСИИ. PYTHON x64 ИМЕЕТ ВОЗМОЖНОСТЬ ВЗЗАИМОДЕЙСТВИЯ С x32 UIO ОБЪЕКТАМИ, НО МЫ РЕКОМЕНДУЕМ ДОПОЛНИТЕЛЬНО ИСПОЛЬЗОВАТЬ ИНТЕРПРЕТАТОР PYTHON x32 (ПОДРОБНЕЕ СМ. ФУНКЦИЮ Configure()) @@ -403,6 +406,8 @@ def UIOSelectorsSecs_WaitDisappear_List (inSpecificationListList,inWaitSecs=8640 :type inSpecificationListList: list, обязательный :param inWaitSecs: Количество секунд, которые отвести на ожидание исчезновения UIO объектов. По умолчанию 24 часа (86400 секунд) :type inWaitSecs: float, необязательный + :param inFlagRaiseException: True - формировать ошибку exception, если платформа не обнаружина ни одного UIO объекта по заданному UIO селектору. False - обратный случай (может привести к ошибочным результатам). По умолчанию True. + :type inFlagRaiseException: bool, опциональный :param inFlagWaitAllInMoment: True - Ожидать до того момента, пока не исчезнут все запрашиваемые UIO объекты на рабочей области :return: Список индексов, которые указывают на номер входящих UIO селекторов, которые были обнаружены на рабочей области. Пример: [0,2] ''' @@ -418,7 +423,7 @@ def UIOSelectorsSecs_WaitDisappear_List (inSpecificationListList,inWaitSecs=8640 #Итерация проверки lIndex = 0 for lItem in inSpecificationListList: - lItemResultFlag=UIOSelector_Exist_Bool(lItem) + lItemResultFlag=UIOSelector_Exist_Bool(lItem,inFlagRaiseException=inFlagRaiseException) #Если обнаружен элемент - добавить его индекс в массив if not lItemResultFlag: lResultList.append(lIndex) @@ -438,7 +443,7 @@ def UIOSelectorsSecs_WaitDisappear_List (inSpecificationListList,inWaitSecs=8640 return lResultList #old: - -def UIOSelectorSecs_WaitAppear_Bool (inSpecificationList,inWaitSecs): +def UIOSelectorSecs_WaitAppear_Bool (inSpecificationList,inWaitSecs, inFlagRaiseException=True): '''L-,W+: Ожидать появление 1-го UIO объекта по заданному UIO селектору !ВНИМАНИЕ! ДАННАЯ ФУНКЦИОНАЛЬНОСТЬ В АВТОМАТИЧЕСКОМ РЕЖИМЕ ПОДДЕРЖИВАЕТ ВСЕ РАЗРЯДНОСТИ ПРИЛОЖЕНИЙ (32|64), КОТОРЫЕ ЗАПУЩЕНЫ В СЕСИИ. PYTHON x64 ИМЕЕТ ВОЗМОЖНОСТЬ ВЗЗАИМОДЕЙСТВИЯ С x32 UIO ОБЪЕКТАМИ, НО МЫ РЕКОМЕНДУЕМ ДОПОЛНИТЕЛЬНО ИСПОЛЬЗОВАТЬ ИНТЕРПРЕТАТОР PYTHON x32 (ПОДРОБНЕЕ СМ. ФУНКЦИЮ Configure()) @@ -455,16 +460,18 @@ def UIOSelectorSecs_WaitAppear_Bool (inSpecificationList,inWaitSecs): :type inSpecificationList: list, обязательный :param inWaitSecs: Количество секунд, которые отвести на ожидание UIO объекта. По умолчанию 24 часа (86400 секунд) :type inWaitSecs: float, необязательный + :param inFlagRaiseException: True - формировать ошибку exception, если платформа не обнаружина ни одного UIO объекта по заданному UIO селектору. False - обратный случай (может привести к ошибочным результатам). По умолчанию True. + :type inFlagRaiseException: bool, опциональный :return: True - UIO объект был обнаружен. False - обратная ситуациая ''' - lWaitAppearList=UIOSelectorsSecs_WaitAppear_List([inSpecificationList],inWaitSecs) + lWaitAppearList=UIOSelectorsSecs_WaitAppear_List([inSpecificationList],inWaitSecs, inFlagRaiseException=inFlagRaiseException) lResult=False if len(lWaitAppearList)>0: lResult=True return lResult #old name - - -def UIOSelectorSecs_WaitDisappear_Bool (inSpecificationList,inWaitSecs): +def UIOSelectorSecs_WaitDisappear_Bool (inSpecificationList,inWaitSecs,inFlagRaiseException=True): '''L-,W+: Ожидать исчезновение 1-го UIO объекта по заданному UIO селектору !ВНИМАНИЕ! ДАННАЯ ФУНКЦИОНАЛЬНОСТЬ В АВТОМАТИЧЕСКОМ РЕЖИМЕ ПОДДЕРЖИВАЕТ ВСЕ РАЗРЯДНОСТИ ПРИЛОЖЕНИЙ (32|64), КОТОРЫЕ ЗАПУЩЕНЫ В СЕСИИ. PYTHON x64 ИМЕЕТ ВОЗМОЖНОСТЬ ВЗЗАИМОДЕЙСТВИЯ С x32 UIO ОБЪЕКТАМИ, НО МЫ РЕКОМЕНДУЕМ ДОПОЛНИТЕЛЬНО ИСПОЛЬЗОВАТЬ ИНТЕРПРЕТАТОР PYTHON x32 (ПОДРОБНЕЕ СМ. ФУНКЦИЮ Configure()) @@ -481,9 +488,11 @@ def UIOSelectorSecs_WaitDisappear_Bool (inSpecificationList,inWaitSecs): :type inSpecificationList: list, обязательный :param inWaitSecs: Количество секунд, которые отвести на исчезновение UIO объекта. По умолчанию 24 часа (86400 секунд) :type inWaitSecs: float, необязательный + :param inFlagRaiseException: True - формировать ошибку exception, если платформа не обнаружина ни одного UIO объекта по заданному UIO селектору. False - обратный случай (может привести к ошибочным результатам). По умолчанию True. + :type inFlagRaiseException: bool, опциональный :return: True - UIO объект был обнаружен. False - обратная ситуациая ''' - lWaitDisappearList=UIOSelectorsSecs_WaitDisappear_List([inSpecificationList],inWaitSecs) + lWaitDisappearList=UIOSelectorsSecs_WaitDisappear_List([inSpecificationList],inWaitSecs,inFlagRaiseException=inFlagRaiseException) lResult=False if len(lWaitDisappearList)>0: lResult=True @@ -912,7 +921,7 @@ def UIO_GetCtrlIndex_Int(inElement): return lResult #old: - PywinautoExtElementsGetInfo -def UIOSelector_Get_UIOInfoList (inUIOSelector, inElement=None): +def UIOSelector_Get_UIOInfoList (inUIOSelector, inElement=None, inFlagRaiseException=True): """L-,W+: Техническая функция: Получить список параметров последних уровней UIO селектора по UIO объектам, которые удовлетворяют входящим inUIOSelector, поиск по которым будет производится от уровня inElement. !ВНИМАНИЕ! ДАННАЯ ФУНКЦИОНАЛЬНОСТЬ В АВТОМАТИЧЕСКОМ РЕЖИМЕ ПОДДЕРЖИВАЕТ ВСЕ РАЗРЯДНОСТИ ПРИЛОЖЕНИЙ (32|64), КОТОРЫЕ ЗАПУЩЕНЫ В СЕСИИ. PYTHON x64 ИМЕЕТ ВОЗМОЖНОСТЬ ВЗЗАИМОДЕЙСТВИЯ С x32 UIO ОБЪЕКТАМИ, НО МЫ РЕКОМЕНДУЕМ ДОПОЛНИТЕЛЬНО ИСПОЛЬЗОВАТЬ ИНТЕРПРЕТАТОР PYTHON x32 (ПОДРОБНЕЕ СМ. ФУНКЦИЮ Configure()) @@ -929,13 +938,15 @@ def UIOSelector_Get_UIOInfoList (inUIOSelector, inElement=None): :type inUIOSelector: list, обязательный :param inElement: UIO объект, от которого выполнить поиск дочерних UIO объектов по UIO селектору inUIOSelector. По умолчанию None - поиск среди всех приложений. :type inElement: UIO объект, необязательный + :param inFlagRaiseException: True - формировать ошибку exception, если платформа не обнаружина ни одного UIO объекта по заданному UIO селектору. False - обратный случай (может привести к ошибочным результатам). По умолчанию True. + :type inFlagRaiseException: bool, опциональный :return: dict, пример: {"title":None,"rich_text":None,"process_id":None,"process":None,"handle":None,"class_name":None,"control_type":None,"control_id":None,"rectangle":{"left":None,"top":None,"right":None,"bottom":None}, 'runtime_id':None} """ #Check the bitness lSafeOtherProcess = UIOSelector_SafeOtherGet_Process(inUIOSelector) if lSafeOtherProcess is None: #Получить родительский объект если на вход ничего не поступило - lResultList=UIOSelector_Get_UIOList(inUIOSelector, inElement) + lResultList=UIOSelector_Get_UIOList(inUIOSelector, inElement, inFlagRaiseException) lIterator = 0 for lItem in lResultList: lResultList[lIterator]=UIOEI_Convert_UIOInfo(lResultList[lIterator].element_info) @@ -989,7 +1000,7 @@ def UIOSelector_TryRestore_Dict(inSpecificationList): return lResult #old: - ElementActionGetList -def UIOSelector_Get_UIOActivityList (inUIOSelector): +def UIOSelector_Get_UIOActivityList (inUIOSelector,inFlagRaiseException=True): """L-,W+: Получить список доступных действий/функций по UIO селектору inUIOSelector. Описание возможных активностей см. ниже. !ВНИМАНИЕ! ДАННАЯ ФУНКЦИОНАЛЬНОСТЬ В АВТОМАТИЧЕСКОМ РЕЖИМЕ ПОДДЕРЖИВАЕТ ВСЕ РАЗРЯДНОСТИ ПРИЛОЖЕНИЙ (32|64), КОТОРЫЕ ЗАПУЩЕНЫ В СЕСИИ. PYTHON x64 ИМЕЕТ ВОЗМОЖНОСТЬ ВЗЗАИМОДЕЙСТВИЯ С x32 UIO ОБЪЕКТАМИ, НО МЫ РЕКОМЕНДУЕМ ДОПОЛНИТЕЛЬНО ИСПОЛЬЗОВАТЬ ИНТЕРПРЕТАТОР PYTHON x32 (ПОДРОБНЕЕ СМ. ФУНКЦИЮ Configure()) @@ -1004,12 +1015,14 @@ def UIOSelector_Get_UIOActivityList (inUIOSelector): :param inUIOSelector: UIO селектор, который определяет UIO объект, для которого будет представлен перечень доступных активностей. :type inUIOSelector: list, обязательный + :param inFlagRaiseException: True - формировать ошибку exception, если платформа не обнаружина ни одного UIO объекта по заданному UIO селектору. False - обратный случай (может привести к ошибочным результатам). По умолчанию True. + :type inFlagRaiseException: bool, опциональный """ #Check the bitness lSafeOtherProcess = UIOSelector_SafeOtherGet_Process(inUIOSelector) if lSafeOtherProcess is None: #Получить объект - lObject=UIOSelector_Get_UIO(inUIOSelector) + lObject=UIOSelector_Get_UIO(inUIOSelector,inFlagRaiseException=inFlagRaiseException) lActionList=dir(lObject) lResult=dir(lObject) #Выполнить чистку списка от неактуальных методов @@ -1037,7 +1050,7 @@ def UIOSelector_Get_UIOActivityList (inUIOSelector): return lResult #old: - ElementRunAction -def UIOSelectorUIOActivity_Run_Dict(inUIOSelector, inActionName, inArgumentList=None, inkwArgumentObject=None): +def UIOSelectorUIOActivity_Run_Dict(inUIOSelector, inActionName, inFlagRaiseException=True, inArgumentList=None, inkwArgumentObject=None): """L-,W+: Выполнить активность inActionName над UIO объектом, полученным с помощью UIO селектора inUIOSelector. Описание возможных активностей см. ниже. !ВНИМАНИЕ! ДАННАЯ ФУНКЦИОНАЛЬНОСТЬ В АВТОМАТИЧЕСКОМ РЕЖИМЕ ПОДДЕРЖИВАЕТ ВСЕ РАЗРЯДНОСТИ ПРИЛОЖЕНИЙ (32|64), КОТОРЫЕ ЗАПУЩЕНЫ В СЕСИИ. PYTHON x64 ИМЕЕТ ВОЗМОЖНОСТЬ ВЗЗАИМОДЕЙСТВИЯ С x32 UIO ОБЪЕКТАМИ, НО МЫ РЕКОМЕНДУЕМ ДОПОЛНИТЕЛЬНО ИСПОЛЬЗОВАТЬ ИНТЕРПРЕТАТОР PYTHON x32 (ПОДРОБНЕЕ СМ. ФУНКЦИЮ Configure()) @@ -1054,6 +1067,8 @@ def UIOSelectorUIOActivity_Run_Dict(inUIOSelector, inActionName, inArgumentList= :type inUIOSelector: list, обязательный :param inActionName: наименование активности, которую требуется выполнить над UIO объектом :type inActionName: str, обязательный + :param inFlagRaiseException: True - формировать ошибку exception, если платформа не обнаружина ни одного UIO объекта по заданному UIO селектору. False - обратный случай (может привести к ошибочным результатам). По умолчанию True. + :type inFlagRaiseException: bool, опциональный :param inArgumentList: список передаваемых неименованных аргументов в функцию inActionName :type inArgumentList: list, необязательный :param inkwArgumentObject: словарь передаваемых именованных аргументов в функцию inActionName @@ -1068,7 +1083,7 @@ def UIOSelectorUIOActivity_Run_Dict(inUIOSelector, inActionName, inArgumentList= #Run activity if SafeOtherProcess is None if lSafeOtherProcess is None: #Определить объект - lObject=UIOSelector_Get_UIO(inUIOSelector) + lObject=UIOSelector_Get_UIO(inUIOSelector,inFlagRaiseException=inFlagRaiseException) #Получить метод для вызова lFunction = getattr(lObject, inActionName) #Выполнить действие diff --git a/Tools/Jupyter-notebooks/start.cmd b/Tools/Jupyter-notebooks/start.cmd index 8484e1de..49547a08 100755 --- a/Tools/Jupyter-notebooks/start.cmd +++ b/Tools/Jupyter-notebooks/start.cmd @@ -1,5 +1,3 @@ chcp 65001 call init-python-env.cmd jupyter-notebook.exe jupyter-notebook.exe - -jupyter-notebook.exe -m notebook -pause>nul \ No newline at end of file +jupyter-notebook.exe -m notebook --notebook-dir="%~dp0 diff --git a/changelog.md b/changelog.md index 67acea22..8f223ea8 100755 --- a/changelog.md +++ b/changelog.md @@ -36,6 +36,7 @@ AGT - AGENT - - pyOpenRPA.Robot.UIWeb.UIOSelectorClick - исправлено. Теперь работает и с XPath, и с CSS - - Добавлена новая функция - pyOpenRPA.Robot.UIWeb.UIOSelectorSetValue. Изменение атрибута value по заданному UIOSelector элемента - - pyOpenRPA.Robot.UIDesktop.UIOSelector_Get_UIOList - исправлена обработка exceptions. Теперь флаг ловит все исключения +- - Флаг inFlagRaiseException добавлен во все функции, связанные с pyOpenRPA.Robot.UIDesktop.UIOSelector_Get_UIOList. Теперь режим работы с исключениями задается глобально. Исключения отлавливаются - inFlagRaiseException=True, не отлавливаются - inFlagRaiseException=False [1.3.1] - ОРКЕСТРАТОР From 15c617d94822a9b0178e62d57bc8b3795c09a8e1 Mon Sep 17 00:00:00 2001 From: Vladislav Klychkov Date: Mon, 13 Mar 2023 15:49:20 +0300 Subject: [PATCH 09/11] + sphinx docs UIWeb fix --- Sources/pyOpenRPA/Robot/UIWeb.py | 78 ++++++++++++++++++++++++-------- changelog.md | 5 +- 2 files changed, 63 insertions(+), 20 deletions(-) diff --git a/Sources/pyOpenRPA/Robot/UIWeb.py b/Sources/pyOpenRPA/Robot/UIWeb.py index 088a01e0..8c59e09e 100755 --- a/Sources/pyOpenRPA/Robot/UIWeb.py +++ b/Sources/pyOpenRPA/Robot/UIWeb.py @@ -16,7 +16,7 @@ UIO_WAIT_INTERVAL_SEC_FLOAT = 1.0 gBrowser:webdriver.Chrome = None def BrowserChromeStart(inDriverExePathStr:str = None, inChromeExePathStr:str = None, inExtensionPathList:list = None, inProfilePathStr:str=None, inSaveAsPDFBool = False, inSavefileDefaultDirStr = None) -> webdriver.Chrome: - """L+,W+: Выполнить запуск браузера Chrome. Если вы скачали pyOpenRPA вместе с репозиторием, то будет использоваться встроенный браузер Google Chrome. Если установка pyOpenRPA производилась другим способом, то требуется указать расположение браузера Google Chrome и соответствующего WebDriver. + """L+,W+: Выполнить запуск браузера Chrome. Если вы скачали pyOpenRPA вместе с репозиторием, то будет использоваться встроенный браузер Google Chrome. Если установка pyOpenRPA производилась другим способом, то требуется указать расположение браузера Google Chrome и соответствующего WebDriver .. code-block:: python @@ -122,6 +122,8 @@ def BrowserChange(inBrowser): def PageOpen(inURLStr: str): """L+,W+: Открыть страницу inURLStr в браузере и дождаться ее загрузки. + !ВНИМАНИЕ! Для работы необходимо проинициализировать webdriver + .. code-block:: python # UIWeb: Взаимодействие с ui web @@ -139,6 +141,8 @@ def PageOpen(inURLStr: str): def PagePrint(): """L+,W+: Открыть окно печати браузера. + !ВНИМАНИЕ! Для работы необходимо проинициализировать webdriver + .. code-block:: python # UIWeb: Взаимодействие с ui web @@ -155,6 +159,8 @@ def PagePrint(): def PageScrollTo(inVerticalPxInt=0, inHorizontalPxInt=0): """L+,W+: Выполнить прокрутку страницы (по вертикали или по горизонтали) + + !ВНИМАНИЕ! Для работы необходимо проинициализировать webdriver .. code-block:: python @@ -174,6 +180,8 @@ def PageScrollTo(inVerticalPxInt=0, inHorizontalPxInt=0): def PageJSExecute(inJSStr, *inArgList): """L+,W+: Отправить на выполнение на сторону браузера код JavaScript. + + !ВНИМАНИЕ! Для работы необходимо проинициализировать webdriver !ВНИМАНИЕ! Данная функция поддерживает передачу переменных в область кода JavaScript (*inArgList). Обратиться к переданным переменным из JavaScript можно с помощью ключевого слова: arguments[i], где i - это порядковый номер переданной переменной @@ -214,6 +222,8 @@ def BrowserClose(): def UIOSelectorList(inUIOSelectorStr, inUIO=None) -> list: """L+,W+: Получить список UIO объектов по UIO селектору. + + !ВНИМАНИЕ! Для работы необходимо проинициализировать webdriver .. code-block:: python @@ -221,7 +231,7 @@ def UIOSelectorList(inUIOSelectorStr, inUIO=None) -> list: from pyOpenRPA.Robot import UIWeb UIWeb.BrowserChromeStart() UIWeb.PageOpen("https://mail.ru") - lUIOSelectorStr = "//*[@id=\"grid\"]/div[2]/div[2]/div[3]/div[1]/ul/li[5]/div/a" + lUIOSelectorStr = "//*[@id=\'grid\']/div[2]/div[2]/div[3]/div[1]/ul/li[5]/div/a" lUIOList = UIWeb.UIOSelectorList(inUIOSelectorStr = lUIOSelectorStr) UIWeb.BrowserClose() @@ -249,6 +259,8 @@ def UIOSelectorList(inUIOSelectorStr, inUIO=None) -> list: def UIOSelectorFirst(inUIOSelectorStr, inUIO=None) -> list: """L+,W+: Получить UIO объект по UIO селектору. + + !ВНИМАНИЕ! Для работы необходимо проинициализировать webdriver .. code-block:: python @@ -256,7 +268,7 @@ def UIOSelectorFirst(inUIOSelectorStr, inUIO=None) -> list: from pyOpenRPA.Robot import UIWeb UIWeb.BrowserChromeStart() UIWeb.PageOpen("https://mail.ru") - lUIOSelectorStr = "//*[@id=\"grid\"]/div[2]/div[2]/div[3]/div[1]/ul/li[5]/div/a" + lUIOSelectorStr = "//*[@id=\'grid\']/div[2]/div[2]/div[3]/div[1]/ul/li[5]/div/a" lUIO = UIWeb.UIOSelectorFirst(inUIOSelectorStr = lUIOSelectorStr) UIWeb.BrowserClose() @@ -274,6 +286,8 @@ def UIOSelectorFirst(inUIOSelectorStr, inUIO=None) -> list: def UIOTextGet(inUIO) -> str: """L+,W+: Получить текст UI элемента. + + !ВНИМАНИЕ! Для работы необходимо проинициализировать webdriver .. code-block:: python @@ -281,7 +295,7 @@ def UIOTextGet(inUIO) -> str: from pyOpenRPA.Robot import UIWeb UIWeb.BrowserChromeStart() UIWeb.PageOpen("https://mail.ru") - lUIOSelectorStr = "//*[@id=\"grid\"]/div[2]/div[2]/div[3]/div[1]/ul/li[5]/div/a" + lUIOSelectorStr = "//*[@id=\'grid\']/div[2]/div[2]/div[3]/div[1]/ul/li[5]/div/a" lUIO = UIWeb.UIOSelectorList(inUIOSelectorStr = lUIOSelectorStr)[0] lTextStr = UIWeb.UIOTextGet(inUIO=lUIO) UIWeb.BrowserClose() @@ -295,6 +309,8 @@ def UIOTextGet(inUIO) -> str: def UIOAttributeGet(inUIO, inAttributeStr) -> str: """L+,W+: Получить обычный (нестилевой) атрибут у UI элемента. + + !ВНИМАНИЕ! Для работы необходимо проинициализировать webdriver .. code-block:: python @@ -302,7 +318,7 @@ def UIOAttributeGet(inUIO, inAttributeStr) -> str: from pyOpenRPA.Robot import UIWeb UIWeb.BrowserChromeStart() UIWeb.PageOpen("https://mail.ru") - lUIOSelectorStr = "//*[@id=\"grid\"]/div[2]/div[2]/div[3]/div[1]/ul/li[5]/div/a" + lUIOSelectorStr = "//*[@id=\'grid\']/div[2]/div[2]/div[3]/div[1]/ul/li[5]/div/a" lUIO = UIWeb.UIOSelectorList(inUIOSelectorStr = lUIOSelectorStr)[0] UIWeb.UIOAttributeGet(inUIO=lUIO, inAttributeStr = "href") UIWeb.BrowserClose() @@ -318,6 +334,8 @@ def UIOAttributeGet(inUIO, inAttributeStr) -> str: def UIOAttributeStyleGet(inUIO, inAttributeStr) -> str: """L+,W+: Получить стилевой атрибут у UI элемента. + + !ВНИМАНИЕ! Для работы необходимо проинициализировать webdriver .. code-block:: python @@ -325,7 +343,7 @@ def UIOAttributeStyleGet(inUIO, inAttributeStr) -> str: from pyOpenRPA.Robot import UIWeb UIWeb.BrowserChromeStart() UIWeb.PageOpen("https://mail.ru") - lUIOSelectorStr = "//*[@id=\"grid\"]/div[2]/div[2]/div[3]/div[1]/ul/li[5]/div/a" + lUIOSelectorStr = "//*[@id=\'grid\']/div[2]/div[2]/div[3]/div[1]/ul/li[5]/div/a" lUIO = UIWeb.UIOSelectorList(inUIOSelectorStr = lUIOSelectorStr)[0] UIWeb.UIOAttributeStyleGet(inUIO=lUIO, inAttributeStr = "href") UIWeb.BrowserClose() @@ -341,6 +359,8 @@ def UIOAttributeStyleGet(inUIO, inAttributeStr) -> str: def UIOAttributeSet(inUIO, inAttributeStr, inValue): """L+,W+: Установить обычный (нестилевой) атрибут у UI элемента. + + !ВНИМАНИЕ! Для работы необходимо проинициализировать webdriver .. code-block:: python @@ -348,7 +368,7 @@ def UIOAttributeSet(inUIO, inAttributeStr, inValue): from pyOpenRPA.Robot import UIWeb UIWeb.BrowserChromeStart() UIWeb.PageOpen("https://mail.ru") - lUIOSelectorStr = "//*[@id=\"grid\"]/div[2]/div[2]/div[3]/div[1]/ul/li[5]/div/a" + lUIOSelectorStr = "//*[@id=\'grid\']/div[2]/div[2]/div[3]/div[1]/ul/li[5]/div/a" lUIO = UIWeb.UIOSelectorList(inUIOSelectorStr = lUIOSelectorStr)[0] UIWeb.UIOAttributeSet(inUIO=lUIO, inAttributeStr = "href", inValue = "https://mail.ru") UIWeb.BrowserClose() @@ -366,6 +386,8 @@ def UIOAttributeSet(inUIO, inAttributeStr, inValue): def UIOAttributeRemove(inUIO, inAttributeStr): """L+,W+: Удалить обычный (нестилевой) атрибут у UI элемента. + + !ВНИМАНИЕ! Для работы необходимо проинициализировать webdriver .. code-block:: python @@ -373,7 +395,7 @@ def UIOAttributeRemove(inUIO, inAttributeStr): from pyOpenRPA.Robot import UIWeb UIWeb.BrowserChromeStart() UIWeb.PageOpen("https://mail.ru") - lUIOSelectorStr = "//*[@id=\"grid\"]/div[2]/div[2]/div[3]/div[1]/ul/li[5]/div/a" + lUIOSelectorStr = "//*[@id=\'grid\']/div[2]/div[2]/div[3]/div[1]/ul/li[5]/div/a" lUIO = UIWeb.UIOSelectorList(inUIOSelectorStr = lUIOSelectorStr)[0] UIWeb.UIOAttributeRemove(lUIO, "href") UIWeb.BrowserClose() @@ -389,6 +411,8 @@ def UIOAttributeRemove(inUIO, inAttributeStr): def UIOAttributeStyleSet(inUIO, inAttributeStr, inValue): """L+,W+: Установить стилевой атрибут у UI элемента. + + !ВНИМАНИЕ! Для работы необходимо проинициализировать webdriver .. code-block:: python @@ -396,7 +420,7 @@ def UIOAttributeStyleSet(inUIO, inAttributeStr, inValue): from pyOpenRPA.Robot import UIWeb UIWeb.BrowserChromeStart() UIWeb.PageOpen("https://mail.ru") - lUIOSelectorStr = "//*[@id=\"grid\"]/div[2]/div[2]/div[3]/div[1]/ul/li[5]/div/a" + lUIOSelectorStr = "//*[@id=\'grid\']/div[2]/div[2]/div[3]/div[1]/ul/li[5]/div/a" lUIO = UIWeb.UIOSelectorList(inUIOSelectorStr = lUIOSelectorStr)[0] UIWeb.UIOAttributeStyleSet(inUIO=lUIO, inAttributeStr = "color", inValue = "grey") UIWeb.BrowserClose() @@ -414,6 +438,8 @@ def UIOAttributeStyleSet(inUIO, inAttributeStr, inValue): def UIOAttributeStyleRemove(inUIO, inAttributeStr:str): """L+,W+: Удалить стилевой атрибут у UI элемента. + + !ВНИМАНИЕ! Для работы необходимо проинициализировать webdriver .. code-block:: python @@ -421,7 +447,7 @@ def UIOAttributeStyleRemove(inUIO, inAttributeStr:str): from pyOpenRPA.Robot import UIWeb UIWeb.BrowserChromeStart() UIWeb.PageOpen("https://mail.ru") - lUIOSelectorStr = "//*[@id=\"grid\"]/div[2]/div[2]/div[3]/div[1]/ul/li[5]/div/a" + lUIOSelectorStr = "//*[@id=\'grid\']/div[2]/div[2]/div[3]/div[1]/ul/li[5]/div/a" lUIO = UIWeb.UIOSelectorList(inUIOSelectorStr = lUIOSelectorStr)[0] UIWeb.UIOAttributeStyleRemove(lUIO, "color") UIWeb.BrowserClose() @@ -437,6 +463,8 @@ def UIOAttributeStyleRemove(inUIO, inAttributeStr:str): def UIOClick(inUIO): """L+,W+: Выполнить нажатие по элементу inUIO. + + !ВНИМАНИЕ! Для работы необходимо проинициализировать webdriver .. code-block:: python @@ -444,7 +472,7 @@ def UIOClick(inUIO): from pyOpenRPA.Robot import UIWeb UIWeb.BrowserChromeStart() UIWeb.PageOpen("https://mail.ru") - lUIOSelectorStr = "//*[@id=\"grid\"]/div[2]/div[2]/div[3]/div[1]/ul/li[5]/div/a" + lUIOSelectorStr = "//*[@id=\'grid\']/div[2]/div[2]/div[3]/div[1]/ul/li[5]/div/a" lUIO = UIWeb.UIOSelectorList(inUIOSelectorStr = lUIOSelectorStr)[0] UIOClick(inUIO = lUIO) UIWeb.BrowserClose() @@ -456,6 +484,8 @@ def UIOClick(inUIO): def UIOSelectorHighlight(inUIOSelectorStr: str, inIsFirst:bool=False, inDurationSecFloat:float=3.0, inColorStr:str="green"): """L+,W+: Выполнить подсвечивание UI элемента с селектором inUIOSelectorStr. + + !ВНИМАНИЕ! Для работы необходимо проинициализировать webdriver .. code-block:: python @@ -463,7 +493,7 @@ def UIOSelectorHighlight(inUIOSelectorStr: str, inIsFirst:bool=False, inDuration from pyOpenRPA.Robot import UIWeb UIWeb.BrowserChromeStart() UIWeb.PageOpen("https://mail.ru") - lUIOSelectorStr = "//*[@id=\"grid\"]/div[2]/div[2]/div[3]/div[1]/ul/li[5]/div/a" + lUIOSelectorStr = "//*[@id=\'grid\']/div[2]/div[2]/div[3]/div[1]/ul/li[5]/div/a" UIWeb.UIOSelectorHighlight(inUIOSelectorStr = lUIOSelectorStr) UIWeb.BrowserClose() @@ -512,6 +542,8 @@ def UIOSelectorHighlight(inUIOSelectorStr: str, inIsFirst:bool=False, inDuration def UIOSelectorClick(inUIOSelectorStr: str): """L+,W+: Выполнить нажатие по элементу с селектором inUIOSelectorStr. + + !ВНИМАНИЕ! Для работы необходимо проинициализировать webdriver .. code-block:: python @@ -519,7 +551,7 @@ def UIOSelectorClick(inUIOSelectorStr: str): from pyOpenRPA.Robot import UIWeb UIWeb.BrowserChromeStart() UIWeb.PageOpen("https://mail.ru") - lUIOSelectorStr = "//*[@id=\"grid\"]/div[2]/div[2]/div[3]/div[1]/ul/li[5]/div/a" + lUIOSelectorStr = "//*[@id=\'grid\']/div[2]/div[2]/div[3]/div[1]/ul/li[5]/div/a" UIWeb.UIOSelectorClick(inUIOSelectorStr = lUIOSelectorStr) UIWeb.BrowserClose() @@ -533,6 +565,8 @@ def UIOSelectorClick(inUIOSelectorStr: str): def UIOSelectorSetValue(inUIOSelectorStr: str, inValue: str): """L+,W+: Установить значение элемента с селектором inUIOSelectorStr. + + !ВНИМАНИЕ! Для работы необходимо проинициализировать webdriver .. code-block:: python @@ -557,14 +591,16 @@ def UIOSelectorSetValue(inUIOSelectorStr: str, inValue: str): def UIOSelectorWaitAppear(inUIOSelectorStr:str, inWaitSecFloat:float=UIO_WAIT_SEC_FLOAT, inWaitIntervalSecFloat:float = UIO_WAIT_INTERVAL_SEC_FLOAT): """L+,W+: Ожидать появление UI элемента на веб странице (блокирует выполнение потока), заданного по UIO селектору inUIOSelectorStr. Выполнять ожидание на протяжении inWaitSecFloat (по умолчанию 60 сек.). Проверка производится с интервалом inWaitIntervalSecFloat (по умолчанию 1 сек.) - + + !ВНИМАНИЕ! Для работы необходимо проинициализировать webdriver + .. code-block:: python # UIWeb: Взаимодействие с ui web from pyOpenRPA.Robot import UIWeb UIWeb.BrowserChromeStart() UIWeb.PageOpen("https://mail.ru") - lUIOSelectorStr = "//*[@id=\"grid\"]/div[2]/div[2]/div[3]/div[1]/ul/li[5]/div/a" + lUIOSelectorStr = "//*[@id=\'grid\']/div[2]/div[2]/div[3]/div[1]/ul/li[5]/div/a" lAppearUIOList = UIWeb.UIOSelectorWaitAppear(inUIOSelectorStr = lUIOSelectorStr) UIWeb.BrowserClose() @@ -589,14 +625,16 @@ def UIOSelectorWaitAppear(inUIOSelectorStr:str, inWaitSecFloat:float=UIO_WAIT_SE def UIOSelectorWaitDisappear(inUIOSelectorStr:str, inWaitSecFloat:float=UIO_WAIT_SEC_FLOAT, inWaitIntervalSecFloat:float = UIO_WAIT_INTERVAL_SEC_FLOAT): """L+,W+: Ожидать исчезновение UI элемента с веб страницы (блокирует выполнение потока), заданного по UIO селектору inUIOSelectorStr. Выполнять ожидание на протяжении inWaitSecFloat (по умолчанию 60 сек.). Проверка производится с интервалом inWaitIntervalSecFloat (по умолчанию 1 сек.) - + + !ВНИМАНИЕ! Для работы необходимо проинициализировать webdriver + .. code-block:: python # UIWeb: Взаимодействие с ui web from pyOpenRPA.Robot import UIWeb UIWeb.BrowserChromeStart() UIWeb.PageOpen("https://mail.ru") - lUIOSelectorStr = "//*[@id=\"grid\"]/div[2]/div[2]/div[3]/div[1]/ul/li[5]/div/a" + lUIOSelectorStr = "//*[@id=\'grid\']/div[2]/div[2]/div[3]/div[1]/ul/li[5]/div/a" UIWeb.UIOSelectorWaitDisappear(inUIOSelectorStr = lUIOSelectorStr) UIWeb.BrowserClose() @@ -628,7 +666,7 @@ def UIOSelectorDetect(inUIOSelectorStr:str) -> str: # UIWeb: Взаимодействие с ui web from pyOpenRPA.Robot import UIWeb lUIOSelectorStr = "#grid > div.grid-middle > div.grid__main-col.svelte-2y66pa > div.grid_newscol.grid_newscol__more-pulse.svelte-1yvqfic > div.grid__ccol.svelte-1yvqfic > ul > li:nth-child(5) > div > a" - lUIOSelectorStr = "//*[@id=\"grid\"]/div[2]/div[2]/div[3]/div[1]/ul/li[5]/div/a" + lUIOSelectorStr = "//*[@id=\'grid\']/div[2]/div[2]/div[3]/div[1]/ul/li[5]/div/a" lResultStr = UIWeb.UIOSelectorDetect(inUIOSelectorStr = lUIOSelectorStr) :param inUIOSelectorStr: XPATH или CSS селектор UI объекта на web странице. Подсказки по CSS: https://devhints.io/css Подсказки по XPath: https://devhints.io/xpath @@ -648,6 +686,8 @@ def UIOSelectorDetect(inUIOSelectorStr:str) -> str: def UIOMouseSearchInit(): """L+,W+: Инициализирует процесс поиска UI элемента с помощью мыши. Для прекращения поиска необходимо использовать функцию: UIOMouseSearchReturn + + !ВНИМАНИЕ! Для работы необходимо проинициализировать webdriver .. code-block:: python @@ -675,6 +715,8 @@ def UIOMouseSearchInit(): def UIOMouseSearchReturn(): """L+,W+: Возвращает UIO объект, над которым находится указатель мыши. Предварительно должна быть вызвана функция UIWeb.UIOMouseSearchInit + + !ВНИМАНИЕ! Для работы необходимо проинициализировать webdriver .. code-block:: python diff --git a/changelog.md b/changelog.md index 8f223ea8..f3c0b046 100755 --- a/changelog.md +++ b/changelog.md @@ -12,8 +12,9 @@ AGT - AGENT - ОБЩЕЕ - - Utils: Disk - подготовка файлов / папок, если они не обнаружены (полезно при первом запуске, если требуются БД/ Файлы хранилищ) - - Jupyter: запуск из директорий, пути к которым содержат пробелы -- - PyOpenRPA: autodocsumm - добавлено к пакетам. -- - Исправлено неправильное поведение оркестратора при авторизации в браузере Firefox. +- - PyOpenRPA: autodocsumm - добавлено к пакетам +- - Исправлено неправильное поведение оркестратора при авторизации в браузере Firefox +- - Исправлено описание функци в документации, раздел UIWeb - ОРКЕСТРАТОР - - Исправление совместимости URL путей с некорыми ресурсами для отработки в LINUX - - Поддержка многотысячной аудитории, одновременно работающей в панели управления (async server-data server-log with fastapi) From c21e476c83832cf901ff0cc344778cb3f94a3f7a Mon Sep 17 00:00:00 2001 From: Vladislav Klychkov Date: Thu, 16 Mar 2023 09:16:19 +0300 Subject: [PATCH 10/11] new auth form --- Orchestrator/config.py | 2 +- Sources/pyOpenRPA/Orchestrator/Server.py | 38 +++-- .../pyOpenRPA/Resources/Web/orpa/auth.xhtml | 142 ++++++++++++++++++ .../Resources/Web/orpa/badAuth.xhtml | 68 ++------- .../pyOpenRPA/Resources/Web/orpa/header.xhtml | 30 ++-- changelog.md | 2 +- 6 files changed, 200 insertions(+), 82 deletions(-) create mode 100644 Sources/pyOpenRPA/Resources/Web/orpa/auth.xhtml diff --git a/Orchestrator/config.py b/Orchestrator/config.py index 3ae229b1..0d8f441a 100755 --- a/Orchestrator/config.py +++ b/Orchestrator/config.py @@ -44,7 +44,7 @@ else: Orchestrator.OrchestratorLoggerGet().setLevel(logging.INFO) # TEST Add User ND - Add Login ND to superuser of the Orchestrator lUACClientDict = SettingsTemplate.__UACClientAdminCreate__() - gSettings["ServerDict"]["AccessUsers"]["FlagCredentialsAsk"]=False + gSettings["ServerDict"]["AccessUsers"]["FlagCredentialsAsk"]=True Orchestrator.UACUpdate(inGSettings=gSettings, inADLoginStr="ND", inADStr="", inADIsDefaultBool=True, inURLList=[], inRoleHierarchyAllowedDict=lUACClientDict) Orchestrator.UACUpdate(inGSettings=gSettings, inADLoginStr="rpa00", inADStr="", inADIsDefaultBool=True, inURLList=[], inRoleHierarchyAllowedDict=lUACClientDict) # TEST Add User IMaslov - Add Login IMaslov to superuser of the Orchestrator diff --git a/Sources/pyOpenRPA/Orchestrator/Server.py b/Sources/pyOpenRPA/Orchestrator/Server.py index 3f877ad3..ee5c7c2a 100755 --- a/Sources/pyOpenRPA/Orchestrator/Server.py +++ b/Sources/pyOpenRPA/Orchestrator/Server.py @@ -18,9 +18,10 @@ from . import ServerBC # объявление import from fastapi import FastAPI, Form, Request, HTTPException, Depends, Header, Response, Body -from fastapi.responses import PlainTextResponse, HTMLResponse, FileResponse, RedirectResponse +from fastapi.responses import PlainTextResponse, HTMLResponse, FileResponse, RedirectResponse, JSONResponse from fastapi.staticfiles import StaticFiles from fastapi.templating import Jinja2Templates +from fastapi.encoders import jsonable_encoder from starlette.datastructures import MutableHeaders from pydantic import BaseModel import uvicorn @@ -74,11 +75,11 @@ def IdentifyAuthorize(inRequest:Request, inResponse:Response, return lCookieAuthToken ###################################### #Way 2 - try to logon - if len(lHeaderAuthorization) == 2: + if lHeaderAuthorization != ['']: if "AuthExc" in lCookies: raise AuthException() else: - llHeaderAuthorizationDecodedUserPasswordList = base64.b64decode(lHeaderAuthorization[1]).decode("utf-8").split(":") + llHeaderAuthorizationDecodedUserPasswordList = base64.b64decode(lHeaderAuthorization[0]).decode("utf-8").split(":") lUser = llHeaderAuthorizationDecodedUserPasswordList[0] lPassword = llHeaderAuthorizationDecodedUserPasswordList[1] lDomain = "" @@ -114,9 +115,7 @@ def IdentifyAuthorize(inRequest:Request, inResponse:Response, mOpenRPA["Domain"] = lResult["Domain"] mOpenRPA["User"] = lResult["User"] mOpenRPA["IsSuperToken"] = __Orchestrator__.GSettingsGet().get("ServerDict", {}).get("AccessUsers", {}).get("AuthTokensDict", {}).get(mOpenRPA["AuthToken"], {}).get("FlagDoNotExpire", False) - try:inResponse.delete_cookie(key="AuthExc") - except Exception:pass - return lAuthToken + raise ReloadPage(token=lAuthToken) #inRequest.OpenRPASetCookie = {} #New engine of server #inRequest.OpenRPAResponseDict["SetCookies"]["AuthToken"] = lAuthToken @@ -151,27 +150,40 @@ class ErrorException(Exception): self.text = text class AuthException(Exception): - def __init__(self, name: str="AuthTryWindowCreate"): + def __init__(self, name: str="AuthTryWindowCreate"): self.name = name +class ReloadPage(Exception): + def __init__(self, token :str, name: str="AuthToken"): + self.name = name + self.token = token + templates = Jinja2Templates(directory=CrossOS.PathJoinList(CrossOS.PathSplitList(__file__)[:-2] + ["Resources","Web","orpa"])) # Обработчик ошибки авторизации (вывод информации о причинах неудачной авторизации) @app.exception_handler(ErrorException) async def unicorn_exception_handler(request: Request, exc:ErrorException): - response = templates.TemplateResponse(status_code=401,name="badAuth.xhtml", context={"request":request, "errorMsg":exc.text, "title":"ОРКЕСТРАТОР PYOPENRPA", "subtitle":"ПАНЕЛЬ УПРАВЛЕНИЯ", "version":__version__}) - response.set_cookie(key=exc.name,value="True") + response = templates.TemplateResponse(status_code=401,name="badAuth.xhtml", context={"request":request, "errorMsg":exc.text, "title":"ОРКЕСТРАТОР PYOPENRPA", "subtitle":"АВТОРИЗАЦИЯ", "version":__version__}) + response.set_cookie(key="AuthExc",value="True") return response -# Обработчик попытки авторизации (отвечает за вызов окна для ввода пары логин / пароль) -@app.exception_handler(AuthException) -async def unicorn_exception_handler_2(request: Request, exc: AuthException): - response = HTMLResponse(status_code=401, headers={'Content-type':'text/html; charset=utf-8', 'WWW-Authenticate':'Basic'}) +# Обработчик успешной попытки авторизации (обновление страницы + установки куки-токена) +@app.exception_handler(ReloadPage) +async def unicorn_exception_handler_3(request: Request, exc:ReloadPage): + response = HTMLResponse(content="", status_code=200) + response.set_cookie(key=exc.name, value=exc.token) try:response.delete_cookie(key="AuthExc") except Exception:pass return response +# Обработчик попытки авторизации (отвечает за рендер формы для ввода пары логин / пароль) +@app.exception_handler(AuthException) +def unicorn_exception_handler_2(request: Request, exc: AuthException): + response = templates.TemplateResponse(status_code=401,name="auth.xhtml", context={"request":request, "title":"ОРКЕСТРАТОР PYOPENRPA", "subtitle":"АВТОРИЗАЦИЯ", "version":__version__}) + try:response.delete_cookie(key="AuthExc") + except Exception:pass + return response diff --git a/Sources/pyOpenRPA/Resources/Web/orpa/auth.xhtml b/Sources/pyOpenRPA/Resources/Web/orpa/auth.xhtml new file mode 100644 index 00000000..b4a05070 --- /dev/null +++ b/Sources/pyOpenRPA/Resources/Web/orpa/auth.xhtml @@ -0,0 +1,142 @@ + + + + + + + + + + + Оркестратор pyOpenRPA + + + + + + + + + + + + + + + + {% include 'header.xhtml' %} +
+
+
+ + Для доступа к панели управления, пожалуйста, пройдите авторизацию +
+
+
+

+
Логин от учетной записи
+
+ +
+

+
+

+
Пароль от учетной записи
+
+ +
+

+
+ +
+
+ +
+
+
+ {% include 'footer.xhtml' %} + + + + + + + + + diff --git a/Sources/pyOpenRPA/Resources/Web/orpa/badAuth.xhtml b/Sources/pyOpenRPA/Resources/Web/orpa/badAuth.xhtml index 7e8c0992..0f1e37ca 100644 --- a/Sources/pyOpenRPA/Resources/Web/orpa/badAuth.xhtml +++ b/Sources/pyOpenRPA/Resources/Web/orpa/badAuth.xhtml @@ -17,15 +17,15 @@ - - + + Оркестратор pyOpenRPA - - - - - - + + + + + + @@ -34,68 +34,32 @@ + {% include 'header.xhtml' %} -




+




Внимание

{{errorMsg}}

- +
{% include 'footer.xhtml' %} diff --git a/Sources/pyOpenRPA/Resources/Web/orpa/header.xhtml b/Sources/pyOpenRPA/Resources/Web/orpa/header.xhtml index bf933b79..e0b7cd06 100755 --- a/Sources/pyOpenRPA/Resources/Web/orpa/header.xhtml +++ b/Sources/pyOpenRPA/Resources/Web/orpa/header.xhtml @@ -1,21 +1,21 @@ - - + + - - - - - + + + + + - - - - - - - - + + + + + + + +
diff --git a/changelog.md b/changelog.md index f3c0b046..592ab500 100755 --- a/changelog.md +++ b/changelog.md @@ -29,7 +29,7 @@ AGT - AGENT - - Переработана система обработки AuthTokenStr==None. Теперь поднимается exception в случаях, когда авторизация производилась, но AuthTokenStr==None - - Добавлена новая функция - Orchestrator.OrchestratorIsCredentialsAsk. Определяет была ли произведена авторизация пользователя - - WebURLConnectFolder - переработано. Добавлен флаг обработки ошибки, позволяющий продолжить инициализацию оркестратора, если папки не существует -- - Переработана система авторизации. Добавлена форма, появляющаяся при неудачной попытке авторизации. +- - Переработана система авторизации. Добавлена стартовая страница оркестратора с формой для ввода пары логин / пароль - РОБОТ - - Убрали лишний print из Screen.BoxAnchorRuleCheck - - pyOpenRPA.Robot.UIWeb.BrowserChromeStart - добавлен флаг печати в PDF и директория для сохранения при инициализации браузера From d8c955826f66bfe4b1b734421f11930cc396c049 Mon Sep 17 00:00:00 2001 From: Vladislav Klychkov Date: Thu, 16 Mar 2023 11:46:26 +0300 Subject: [PATCH 11/11] mouseserach return fix --- Sources/pyOpenRPA/Orchestrator/Server.py | 1 + Sources/pyOpenRPA/Robot/UIWeb.py | 5 ++++- changelog.md | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Sources/pyOpenRPA/Orchestrator/Server.py b/Sources/pyOpenRPA/Orchestrator/Server.py index ee5c7c2a..797f1249 100755 --- a/Sources/pyOpenRPA/Orchestrator/Server.py +++ b/Sources/pyOpenRPA/Orchestrator/Server.py @@ -144,6 +144,7 @@ for lItem in lRouteList: tags=["FastAPI"] ) +# объявление классов для дальнейшей обработки вызываемых исключений (обязательно должны наследоваться от EXception) class ErrorException(Exception): def __init__(self, text :str, name: str="AuthExc"): self.name = name diff --git a/Sources/pyOpenRPA/Robot/UIWeb.py b/Sources/pyOpenRPA/Robot/UIWeb.py index 8c59e09e..5b493d9a 100755 --- a/Sources/pyOpenRPA/Robot/UIWeb.py +++ b/Sources/pyOpenRPA/Robot/UIWeb.py @@ -1,6 +1,7 @@ from selenium import * from selenium import webdriver, common from selenium.webdriver.common.by import By +from selenium.common.exceptions import JavascriptException import os import sys import json @@ -737,5 +738,7 @@ def UIOMouseSearchReturn(): document.removeEventListener('mousemove', document.ORPASearch); return document.elementFromPoint(document.ORPAMouseXInt,document.ORPAMouseYInt); """ - return PageJSExecute(lJSStr) + try: + return PageJSExecute(lJSStr) + except JavascriptException: raise JavascriptException("Отсутствуют координаты для идентификации веб-элемента. Пожалуйста, в следующий раз двигайте мышью") \ No newline at end of file diff --git a/changelog.md b/changelog.md index 592ab500..0888046f 100755 --- a/changelog.md +++ b/changelog.md @@ -38,7 +38,7 @@ AGT - AGENT - - Добавлена новая функция - pyOpenRPA.Robot.UIWeb.UIOSelectorSetValue. Изменение атрибута value по заданному UIOSelector элемента - - pyOpenRPA.Robot.UIDesktop.UIOSelector_Get_UIOList - исправлена обработка exceptions. Теперь флаг ловит все исключения - - Флаг inFlagRaiseException добавлен во все функции, связанные с pyOpenRPA.Robot.UIDesktop.UIOSelector_Get_UIOList. Теперь режим работы с исключениями задается глобально. Исключения отлавливаются - inFlagRaiseException=True, не отлавливаются - inFlagRaiseException=False - +- - pyOpenRPA.Robot.UIWeb.UIOMouseSearchReturn добавлена обработка JavascriptException [1.3.1] - ОРКЕСТРАТОР - - минорные правки в дизайн