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 23b1fd58..797f1249 100755 --- a/Sources/pyOpenRPA/Orchestrator/Server.py +++ b/Sources/pyOpenRPA/Orchestrator/Server.py @@ -18,9 +18,11 @@ 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, 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 import io @@ -28,6 +30,7 @@ from starlette.responses import StreamingResponse from typing import Union from pyOpenRPA import __version__ +import requests import base64 import uuid import datetime @@ -43,6 +46,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")): @@ -70,60 +75,62 @@ def IdentifyAuthorize(inRequest:Request, inResponse:Response, return lCookieAuthToken ###################################### #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 lHeaderAuthorization != ['']: + if "AuthExc" in lCookies: + raise AuthException() else: - raise HTTPException(status_code=401, detail="Попытка авторизации не прошла успешно (неверная пара логин / пароль)", headers={}) - ###################################### + llHeaderAuthorizationDecodedUserPasswordList = base64.b64decode(lHeaderAuthorization[0]).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) + raise ReloadPage(token=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={'Content-type':'text/html', 'WWW-Authenticate':'Basic'}) + raise AuthException() else: return None # Credentials are not required - return none - - +# Перевод встроенных fastapi функций на авторизацию lRouteList =[] for lItem in app.router.routes: lRouteList.append(lItem) @@ -137,6 +144,49 @@ for lItem in lRouteList: tags=["FastAPI"] ) +# объявление классов для дальнейшей обработки вызываемых исключений (обязательно должны наследоваться от EXception) +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 + +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="AuthExc",value="True") + return response + +# Обработчик успешной попытки авторизации (обновление страницы + установки куки-токена) +@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 + + from . import ServerSettings @@ -185,13 +235,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/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/Orchestrator/__Orchestrator__.py b/Sources/pyOpenRPA/Orchestrator/__Orchestrator__.py index fba16897..02029da6 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 @@ -1192,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: Глобальный словарь настроек Оркестратора (синглтон) @@ -1220,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/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 new file mode 100644 index 00000000..0f1e37ca --- /dev/null +++ b/Sources/pyOpenRPA/Resources/Web/orpa/badAuth.xhtml @@ -0,0 +1,65 @@ + + + + + + + + + + + Оркестратор pyOpenRPA + + + + + + + + + + + + + + + + {% 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/Sources/pyOpenRPA/Robot/UIDesktop.py b/Sources/pyOpenRPA/Robot/UIDesktop.py index 73142128..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 селектора ''' @@ -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 as e: + if inFlagRaiseException: raise e + else: return [] #old:PywinautoExtElementGet def UIOSelector_Get_UIO (inSpecificationList,inElement=None,inFlagRaiseException=True): @@ -268,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()) @@ -292,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 @@ -299,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: @@ -319,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()) @@ -342,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] ''' @@ -356,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) @@ -376,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()) @@ -399,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] ''' @@ -414,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) @@ -434,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()) @@ -451,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()) @@ -477,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 @@ -908,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()) @@ -925,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) @@ -985,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()) @@ -1000,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) #Выполнить чистку списка от неактуальных методов @@ -1033,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()) @@ -1050,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 @@ -1064,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/Sources/pyOpenRPA/Robot/UIWeb.py b/Sources/pyOpenRPA/Robot/UIWeb.py index 32d34f6d..5b493d9a 100755 --- a/Sources/pyOpenRPA/Robot/UIWeb.py +++ b/Sources/pyOpenRPA/Robot/UIWeb.py @@ -1,8 +1,10 @@ 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 from pyOpenRPA.Tools import CrossOS import time @@ -14,8 +16,8 @@ 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: - """L+,W+: Выполнить запуск браузера Chrome. Если вы скачали pyOpenRPA вместе с репозиторием, то будет использоваться встроенный браузер Google Chrome. Если установка pyOpenRPA производилась другим способом, то требуется указать расположение браузера Google Chrome и соответствующего WebDriver. +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 +34,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 +60,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: @@ -94,6 +123,8 @@ def BrowserChange(inBrowser): def PageOpen(inURLStr: str): """L+,W+: Открыть страницу inURLStr в браузере и дождаться ее загрузки. + !ВНИМАНИЕ! Для работы необходимо проинициализировать webdriver + .. code-block:: python # UIWeb: Взаимодействие с ui web @@ -107,9 +138,30 @@ def PageOpen(inURLStr: str): """ global gBrowser if gBrowser is not None: gBrowser.get(inURLStr) + +def PagePrint(): + """L+,W+: Открыть окно печати браузера. + + !ВНИМАНИЕ! Для работы необходимо проинициализировать webdriver + + .. 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+: Выполнить прокрутку страницы (по вертикали или по горизонтали) + + !ВНИМАНИЕ! Для работы необходимо проинициализировать webdriver .. code-block:: python @@ -129,6 +181,8 @@ def PageScrollTo(inVerticalPxInt=0, inHorizontalPxInt=0): def PageJSExecute(inJSStr, *inArgList): """L+,W+: Отправить на выполнение на сторону браузера код JavaScript. + + !ВНИМАНИЕ! Для работы необходимо проинициализировать webdriver !ВНИМАНИЕ! Данная функция поддерживает передачу переменных в область кода JavaScript (*inArgList). Обратиться к переданным переменным из JavaScript можно с помощью ключевого слова: arguments[i], где i - это порядковый номер переданной переменной @@ -169,6 +223,8 @@ def BrowserClose(): def UIOSelectorList(inUIOSelectorStr, inUIO=None) -> list: """L+,W+: Получить список UIO объектов по UIO селектору. + + !ВНИМАНИЕ! Для работы необходимо проинициализировать webdriver .. code-block:: python @@ -176,7 +232,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() @@ -204,6 +260,8 @@ def UIOSelectorList(inUIOSelectorStr, inUIO=None) -> list: def UIOSelectorFirst(inUIOSelectorStr, inUIO=None) -> list: """L+,W+: Получить UIO объект по UIO селектору. + + !ВНИМАНИЕ! Для работы необходимо проинициализировать webdriver .. code-block:: python @@ -211,7 +269,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() @@ -229,6 +287,8 @@ def UIOSelectorFirst(inUIOSelectorStr, inUIO=None) -> list: def UIOTextGet(inUIO) -> str: """L+,W+: Получить текст UI элемента. + + !ВНИМАНИЕ! Для работы необходимо проинициализировать webdriver .. code-block:: python @@ -236,7 +296,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() @@ -250,6 +310,8 @@ def UIOTextGet(inUIO) -> str: def UIOAttributeGet(inUIO, inAttributeStr) -> str: """L+,W+: Получить обычный (нестилевой) атрибут у UI элемента. + + !ВНИМАНИЕ! Для работы необходимо проинициализировать webdriver .. code-block:: python @@ -257,7 +319,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() @@ -273,6 +335,8 @@ def UIOAttributeGet(inUIO, inAttributeStr) -> str: def UIOAttributeStyleGet(inUIO, inAttributeStr) -> str: """L+,W+: Получить стилевой атрибут у UI элемента. + + !ВНИМАНИЕ! Для работы необходимо проинициализировать webdriver .. code-block:: python @@ -280,7 +344,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() @@ -296,6 +360,8 @@ def UIOAttributeStyleGet(inUIO, inAttributeStr) -> str: def UIOAttributeSet(inUIO, inAttributeStr, inValue): """L+,W+: Установить обычный (нестилевой) атрибут у UI элемента. + + !ВНИМАНИЕ! Для работы необходимо проинициализировать webdriver .. code-block:: python @@ -303,7 +369,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() @@ -321,6 +387,8 @@ def UIOAttributeSet(inUIO, inAttributeStr, inValue): def UIOAttributeRemove(inUIO, inAttributeStr): """L+,W+: Удалить обычный (нестилевой) атрибут у UI элемента. + + !ВНИМАНИЕ! Для работы необходимо проинициализировать webdriver .. code-block:: python @@ -328,7 +396,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() @@ -344,6 +412,8 @@ def UIOAttributeRemove(inUIO, inAttributeStr): def UIOAttributeStyleSet(inUIO, inAttributeStr, inValue): """L+,W+: Установить стилевой атрибут у UI элемента. + + !ВНИМАНИЕ! Для работы необходимо проинициализировать webdriver .. code-block:: python @@ -351,7 +421,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() @@ -369,6 +439,8 @@ def UIOAttributeStyleSet(inUIO, inAttributeStr, inValue): def UIOAttributeStyleRemove(inUIO, inAttributeStr:str): """L+,W+: Удалить стилевой атрибут у UI элемента. + + !ВНИМАНИЕ! Для работы необходимо проинициализировать webdriver .. code-block:: python @@ -376,7 +448,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() @@ -392,6 +464,8 @@ def UIOAttributeStyleRemove(inUIO, inAttributeStr:str): def UIOClick(inUIO): """L+,W+: Выполнить нажатие по элементу inUIO. + + !ВНИМАНИЕ! Для работы необходимо проинициализировать webdriver .. code-block:: python @@ -399,7 +473,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() @@ -411,6 +485,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 @@ -418,7 +494,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() @@ -467,6 +543,8 @@ def UIOSelectorHighlight(inUIOSelectorStr: str, inIsFirst:bool=False, inDuration def UIOSelectorClick(inUIOSelectorStr: str): """L+,W+: Выполнить нажатие по элементу с селектором inUIOSelectorStr. + + !ВНИМАНИЕ! Для работы необходимо проинициализировать webdriver .. code-block:: python @@ -474,25 +552,56 @@ 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() :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. + + !ВНИМАНИЕ! Для работы необходимо проинициализировать webdriver + + .. 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 сек.) - + + !ВНИМАНИЕ! Для работы необходимо проинициализировать 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() @@ -517,14 +626,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() @@ -556,7 +667,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 @@ -576,6 +687,8 @@ def UIOSelectorDetect(inUIOSelectorStr:str) -> str: def UIOMouseSearchInit(): """L+,W+: Инициализирует процесс поиска UI элемента с помощью мыши. Для прекращения поиска необходимо использовать функцию: UIOMouseSearchReturn + + !ВНИМАНИЕ! Для работы необходимо проинициализировать webdriver .. code-block:: python @@ -603,6 +716,8 @@ def UIOMouseSearchInit(): def UIOMouseSearchReturn(): """L+,W+: Возвращает UIO объект, над которым находится указатель мыши. Предварительно должна быть вызвана функция UIWeb.UIOMouseSearchInit + + !ВНИМАНИЕ! Для работы необходимо проинициализировать webdriver .. code-block:: python @@ -623,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/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 6f9fbd43..49547a08 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 diff --git a/changelog.md b/changelog.md index ce53e791..0888046f 100755 --- a/changelog.md +++ b/changelog.md @@ -11,6 +11,10 @@ AGT - AGENT ****************************** - ОБЩЕЕ - - Utils: Disk - подготовка файлов / папок, если они не обнаружены (полезно при первом запуске, если требуются БД/ Файлы хранилищ) +- - Jupyter: запуск из директорий, пути к которым содержат пробелы +- - PyOpenRPA: autodocsumm - добавлено к пакетам +- - Исправлено неправильное поведение оркестратора при авторизации в браузере Firefox +- - Исправлено описание функци в документации, раздел UIWeb - ОРКЕСТРАТОР - - Исправление совместимости URL путей с некорыми ресурсами для отработки в LINUX - - Поддержка многотысячной аудитории, одновременно работающей в панели управления (async server-data server-log with fastapi) @@ -22,9 +26,19 @@ AGT - AGENT - - AgentProcessList исправление (hotfix) - - Права доступа в случае незаявленного пользователя (Hotfix) - - Возможность авторизации в формате 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. Теперь флаг ловит все исключения +- - Флаг inFlagRaiseException добавлен во все функции, связанные с pyOpenRPA.Robot.UIDesktop.UIOSelector_Get_UIOList. Теперь режим работы с исключениями задается глобально. Исключения отлавливаются - inFlagRaiseException=True, не отлавливаются - inFlagRaiseException=False +- - pyOpenRPA.Robot.UIWeb.UIOMouseSearchReturn добавлена обработка JavascriptException [1.3.1] - ОРКЕСТРАТОР - - минорные правки в дизайн