Ivan Maslov 4 years ago
commit 85b3838453

@ -1,68 +1,51 @@
# pyOpenRPA
Powerfull OpenSource RPA tool for business (based on python 3). Best perfomance and absolutely free!
# How to run
Studio
Double click to Studio\StudioRun_32.cmd or Studio\StudioRun_64.cmd
# Robot how to debug
Robot\PythonDebug_64.cmd
import RobotConnector
RobotConnector.ActivityRun(
{
ModuleName: <"UIDesktop"|..., str>,
ActivityName: <Function or procedure name in module, str>,
ArgumentList: [<Argument 1, any type>, ...] - optional,
ArgumentDict: {<Argument 1 name, str>:<Argument 1 value, any type>, ...} - optional
}
)
OR
from pyOpenRPA.Robot import UIDesktop
UIDesktop.<ActivityName>(<args>)
# Robot example script:
Robot\Examples\GetFolderList\Python_32_Script_Run.cmd
# Python 32 bit
Resources\WPy32-3720\python-3.7.2\python.exe
# Python 64 bit
Resources\WPy64-3720\python-3.7.2.amd64\python.exe
# Module UIDesktop activity List:
############################
Новая версия
############################
Получить список элементов, который удовлетворяет условиям через расширенный движок поиска
[
{
"index":<index in parent ui object>,
"depth_start" - search start depth (default is 1)
"depth_end" - search stop depth (по умолчанию 1)
"class_name" - class name attribute for search in ui objects
"title" - title attribute of the ui object
"rich_text" - attribute os the ui object rich_text
}
]
# Open RPA Wiki
## Content
## Donate
pyOpenRPA is absolutely non-commercial project. [Please donate some money if this project is important for you. Link to online donations.](https://money.yandex.ru/to/4100115560661986)
## Road map
- Wiki
- ENG - in progress (see content below), plan date 31.08.2020
- Translate page Theory & practice: Desktop app in english [done 19.08.2020]
- Create page Theory & practice: Keyboard & mouse manipulation [plan 31.08.2020]
- RUS - next step
- Tutorial
- ENG - next step
- RUS - in progress (see content below), plan date 18.09.2020
- Dev actions
- Refactoring the arguments in UIDesktop.py (only in new branch pyOpenRPA version. For backward compatibility purpose), plan date -
## Wiki
In wiki you can find:
- [About OpenRPA, library dependencies and licensing](https://gitlab.com/UnicodeLabs/OpenRPA/wikis/01.-About-OpenRPA,-library-dependencies-and-licensing)
- [Architecture (Studio, Robot, Orchestrator)](https://gitlab.com/UnicodeLabs/OpenRPA/wikis/02.-Architecture-(Studio,-Robot,-Orchestrator))
- [How to install (system requirements)](https://gitlab.com/UnicodeLabs/OpenRPA/wikis/03.-How-to-install-(system-requirements))
- [Tool Studio: How to use](https://gitlab.com/UnicodeLabs/OpenRPA/wikis/04.1.-Tool-Studio:-How-to-use)
- [Tool Robot: How to use](https://gitlab.com/UnicodeLabs/OpenRPA/wikis/04.2.-Tool-Robot:-How-to-use)
- [About pyOpenRPA, library dependencies and licensing](Wiki/01.-About-OpenRPA,-library-dependencies-and-licensing.md)
- [Architecture (Studio, Robot, Orchestrator)](Wiki/02.-Architecture-(Studio,-Robot,-Orchestrator).md)
- [How to install (system requirements)](Wiki/03.-How-to-install-(system-requirements).md)
- [Tool Studio: How to use](Wiki/04.1.-Tool-Studio.-How-to-use.md)
- [Tool Robot: How to use](Wiki/04.2.-Tool-Robot.-How-to-use.md)
- Tool Orchestrator: How to use
- [Theory & practice: Web app access (Chrome, Firefox, Opera)](https://gitlab.com/UnicodeLabs/OpenRPA/wikis/05.1.-Theory-&-practice:-Web-app-access-(Chrome,-Firefox,-Opera))
- [Theory & practice: Desktop app UI access (win32 and UI automation dlls)](https://gitlab.com/UnicodeLabs/OpenRPA/wikis/05.2.-Theory-&-practice:-Desktop-app-UI-access-(win32-and-UI-automation-dlls))
- [Theory & practice: Web app access (Chrome, Firefox, Opera)](Wiki/05.1.-Theory-&-practice.-Web-app-access-(Chrome,-Firefox,-Opera).md)
- [Theory & practice: Desktop app UI access (win32 and UI automation dlls)](Wiki/05.2.-Theory-&-practice.-Desktop-app-UI-access-(win32-and-UI-automation-dlls).md)
- Theory & practice: Keyboard & mouse manipulation
- Theory & practice: Screen capture & image recognition
#Dependencies
## Tutorials
[ENG]
Content in progress<br>
[RUS]
[Перейти в раздел туториалов](Wiki/RUS_Tutorial/README.md)
# Copyrights & Contacts
pyOpenRPA is created by Ivan Maslov (Russia). Use it for free! <br>
*My purpose is to create [#IT4Business](https://www.facebook.com/RU.IT4Business) models.* <br>
If you need IT help feel free to contact me.<br>
## My contacts: <br>
E-mail: Ivan.Maslov@UnicodeLabs.ru<br>
Skype: MegaFinder<br>
Facebook: https://www.facebook.com/RU.Ivan.Maslov<br>
LinkedIn: https://www.linkedin.com/in/RU-IvanMaslov/
# Dependencies
* Python 3 x32 [psutil, pywinauto, wmi, PIL, keyboard, pyautogui, win32api (pywin32), selenium, openCV, tesseract, requests, lxml, PyMuPDF]
* Python 3 x64 [psutil, pywinauto, wmi, PIL, keyboard, pyautogui, win32api (pywin32), selenium, openCV, tesseract, requests, lxml, PyMuPDF]
* pywinauto (Windows GUI automation)
@ -70,4 +53,5 @@ In wiki you can find:
* JsRender by https://www.jsviews.com (switch to Handlebars)
* Handlebars
Created by Ivan Maslov (Russia)
#License
Under the MIT license (absolutely free)

@ -16,6 +16,7 @@ import importlib
from importlib import util
import threading # Multi-threading for RobotRDPActive
from .RobotRDPActive import RobotRDPActive #Start robot rdp active
from .RobotScreenActive import Monitor #Start robot screen active
import uuid # Generate uuid
#Единый глобальный словарь (За основу взять из Settings.py)
@ -54,6 +55,12 @@ lThreadServer = Server.RobotDaemonServer("ServerThread", gSettingsDict)
lThreadServer.start()
if lL: lL.info("Web server has been started") #Logging
# Init the RobotScreenActive in another thread
lRobotScreenActiveThread = threading.Thread(target= Monitor.CheckScreen)
lRobotScreenActiveThread.daemon = True # Run the thread in daemon mode.
lRobotScreenActiveThread.start() # Start the thread execution.
if lL: lL.info("Robot Screen active has been started") #Logging
# Init the RobotRDPActive in another thread
lRobotRDPActiveThread = threading.Thread(target= RobotRDPActive.RobotRDPActive, kwargs={"inGSettings":gSettingsDict})
lRobotRDPActiveThread.daemon = True # Run the thread in daemon mode.

@ -0,0 +1,3 @@
for /f "skip=1 tokens=3" %%s in ('query user %USERNAME%') do (
%windir%\System32\tscon.exe %%s /dest:console
)

@ -0,0 +1,14 @@
import time #lib to create delay
from . import Screen # module to detect screen exists
#Check screen every 1 second
def CheckScreen(inIntervalSeconds=1):
while True:
#Check if screen exist
if not Screen.Exists():
#Send os command to create console version (base screen)
Screen.ConsoleScreenBase()
#Delay to create console screen
time.sleep(2)
#Delay
time.sleep(inIntervalSeconds)
return None

@ -0,0 +1,20 @@
from PIL import ImageGrab
import os # to execute cmd commands
#Check if screen is exists
def Exists():
#Try to get 1 px from screen
try:
#Take 1 px
ImageGrab.grab(bbox=(0,0,1,1))
#Screen is exists - return True
return True
#Catch exception
except Exception:
#Screen does not exists - return false
return False
#Make console session
def ConsoleScreenBase():
#Get script folder path
lFolderPath = "/".join(__file__.split("\\")[:-1])
#Send command to cmd
os.system(os.path.join(lFolderPath,"ConsoleStart.bat"))

@ -0,0 +1,16 @@
#Run example
#cd %~dp0..\..\Sources
#..\Resources\WPy64-3720\python-3.7.2.amd64\python.exe -m pyOpenRPA.Tools.RobotScreenActive
#pause >nul
#Import parent folder to import current / other packages
#########################################################
import sys
import subprocess #start process async
import os #path, run, remove
import time #timer
import importlib
#lFolderPath = "\\".join(__file__.split("\\")[:-4])
lFolderPath = "/".join(__file__.split("/")[:-4])
sys.path.insert(0, lFolderPath)
from pyOpenRPA.Tools.RobotScreenActive import Monitor
Monitor.CheckScreen()

@ -101,6 +101,7 @@ def SettingsUpdate(inGlobalConfiguration):
#}
#Orchestrator basic dependencies
{"Method":"GET", "URL": "/", "MatchType": "EqualCase", "ResponseFilePath": os.path.join(lOrchestratorFolder, "Web\\Index.xhtml"), "ResponseContentType": "text/html"},
{"Method":"GET", "URL": "/Index.js", "MatchType": "EqualCase", "ResponseFilePath": os.path.join(lOrchestratorFolder, "Web\\Index.js"), "ResponseContentType": "text/javascript"},
{"Method":"GET", "URL": "/3rdParty/Semantic-UI-CSS-master/semantic.min.css", "MatchType": "EqualCase", "ResponseFilePath": os.path.join(lOrchestratorFolder, "..\\Resources\\Web\\Semantic-UI-CSS-master\\semantic.min.css"), "ResponseContentType": "text/css"},
{"Method":"GET", "URL": "/3rdParty/Semantic-UI-CSS-master/semantic.min.js", "MatchType": "EqualCase", "ResponseFilePath": os.path.join(lOrchestratorFolder, "..\\Resources\\Web\\Semantic-UI-CSS-master\\semantic.min.js"), "ResponseContentType": "application/javascript"},
{"Method":"GET", "URL": "/3rdParty/jQuery/jquery-3.1.1.min.js", "MatchType": "EqualCase", "ResponseFilePath": os.path.join(lOrchestratorFolder, "..\\Resources\\Web\\jQuery\\jquery-3.1.1.min.js"), "ResponseContentType": "application/javascript"},
@ -110,6 +111,7 @@ def SettingsUpdate(inGlobalConfiguration):
{"Method":"GET", "URL": "/3rdParty/Handlebars/handlebars-v4.1.2.js", "MatchType": "EqualCase", "ResponseFilePath": os.path.join(lOrchestratorFolder, "..\\Resources\\Web\\Handlebars\\handlebars-v4.1.2.js"), "ResponseContentType": "application/javascript"},
{"Method": "GET", "URL": "/Monitor/ControlPanelDictGet", "MatchType": "Equal", "ResponseDefRequestGlobal": Monitor_ControlPanelDictGet, "ResponseContentType": "application/json"},
{"Method": "GET", "URL": "/GetScreenshot", "MatchType": "BeginWith", "ResponseDefRequestGlobal": GetScreenshot, "ResponseContentType": "image/png"},
{"Method": "GET", "URL": "/pyOpenRPA_logo.png", "MatchType": "Equal", "ResponseFilePath": os.path.join(lOrchestratorFolder, "..\\Resources\\Web\\pyOpenRPA_logo.png"), "ResponseContentType": "image/png"},
{"Method": "GET", "URL": "/Orchestrator/RobotRDPActive/ControlPanelDictGet", "MatchType": "Equal","ResponseDefRequestGlobal": RobotRDPActive_ControlPanelDictGet, "ResponseContentType": "application/json"},
{"Method": "POST", "URL": "/Orchestrator/UserRoleHierarchyGet", "MatchType": "Equal","ResponseDefRequestGlobal": UserRoleHierarchyGet, "ResponseContentType": "application/json"}
]

@ -0,0 +1,646 @@
var mGlobal={}
$(document).ready(function() {
// fix main menu to page on passing
$('.main.menu').visibility({
type: 'fixed'
});
$('.overlay').visibility({
type: 'fixed',
offset: 80
});
// lazy load images
$('.image').visibility({
type: 'image',
transition: 'vertical flip in',
duration: 500
});
// show dropdown on hover
$('.main.menu .ui.dropdown').dropdown({
on: 'hover'
});
function clone(obj) {
var copy;
// Handle the 3 simple types, and null or undefined
if (null == obj || "object" != typeof obj) return obj;
// Handle Date
if (obj instanceof Date) {
copy = new Date();
copy.setTime(obj.getTime());
return copy;
}
// Handle Array
if (obj instanceof Array) {
copy = [];
for (var i = 0, len = obj.length; i < len; i++) {
copy[i] = clone(obj[i]);
}
return copy;
}
// Handle Object
if (obj instanceof Object) {
copy = {};
for (var attr in obj) {
if (obj.hasOwnProperty(attr)) copy[attr] = clone(obj[attr]);
}
return copy;
}
throw new Error("Unable to copy obj! Its type isn't supported.");
}
//For data storage key
mGlobal["DataStorage"] = {}
String.prototype.replaceAll = function(search, replace){
return this.split(search).join(replace);
}
mGlobal.GeneralGenerateHTMLCodeHandlebars=function(inInnerTemplateSelector,inData) {
lHTMLTemplate=$(inInnerTemplateSelector)[0].innerHTML
//console.log(lHTMLTemplate)
//Компиляция
var template = Handlebars.compile(lHTMLTemplate);
//Вставка данных
var lHTMLResult = template(inData);
return lHTMLResult
}
mGlobal.GeneralGenerateHTMLCode=function(inTemplateHTMLSelector,inItemDictionary,inKeywordPrefix="::",inKeywordPostfix="::") {
///Получить заготовку
lTemplateHTMLCode=$(inTemplateHTMLSelector)[0].outerHTML
///Определить ключь экранирования специальных ключевых слов
///Выполнить циклические замены, если там есть пожходящие ключи
lResultHTMLCode=lTemplateHTMLCode
for(var lKey in inItemDictionary) {
lHTMLKey=inKeywordPrefix+lKey+inKeywordPostfix;
lResultHTMLCode=lResultHTMLCode.replaceAll(lHTMLKey,inItemDictionary[lKey])
}
///Вернуть результат
return lResultHTMLCode
}
//////////////////////////
/////Info JS module
//////////////////////////
mGlobal.Info={};
mGlobal.Info.TableActivityLogScheduleListRefresh=function() {
}
//////////////////////////
/////Controller JS module
//////////////////////////
mGlobal.Controller={};
mGlobal.Controller.CMDRunText=function(inCMDText) {
///Подготовить конфигурацию
lData = [
{"Type":"CMDStart", "Command": inCMDText}
]
///Обнулить таблицу
$.ajax({
type: "POST",
url: 'Utils/Processor',
data: JSON.stringify(lData),
success:
function(lData,l2,l3){},
dataType: "text"
});
}
mGlobal.Controller.CMDRun=function() {
///Обнулить таблицу
lCMDCode=$(".openrpa-controller-cmd-run-input")[0].value
///Подготовить конфигурацию
lData = [
{"Type":"CMDStart", "Command": lCMDCode }
]
$.ajax({
type: "POST",
url: 'Utils/Processor',
data: JSON.stringify(lData),
success:
function(lData,l2,l3)
{
var lResponseJSON=JSON.parse(lData)
///Отправить запрос на формирование таблицы
//lHTMLCode=console.log("CMDRun result: "+lResponseJSON[0].result)
},
dataType: "text"
});
}
mGlobal.Controller.CMDRunGUILogout=function() {
///Обнулить таблицу
lCMDCode="for /f \"skip=1 tokens=2\" %s in ('query user %USERNAME%') do (tscon \\dest:console)"
//lCMDCode = lCMDCode.replace(/\\n/g, "\\n")
// .replace(/\\'/g, "\\'")
// .replace(/\\"/g, '\\"')
// .replace(/\\&/g, "\\&")
// .replace(/\\r/g, "\\r")
// .replace(/\\t/g, "\\t")
// .replace(/\\b/g, "\\b")
// .replace(/\\f/g, "\\f")
// .replace('"', "\\\"");
///Подготовить конфигурацию
lData = [
{"Type":"CMDStart", "Command": lCMDCode }
]
$.ajax({
type: "POST",
url: 'Utils/Processor',
data: JSON.stringify(lData),
success:
function(lData,l2,l3)
{
var lResponseJSON=JSON.parse(lData)
///Отправить запрос на формирование таблицы
//lHTMLCode=console.log("CMDRun result: "+lResponseJSON[0].result)
},
dataType: "text"
});
}
///Перезагрузить Orchestrator
mGlobal.Controller.OrchestratorRestart=function() {
///Подготовить конфигурацию
lData = [
{"Type":"OrchestratorRestart"}
]
$.ajax({
type: "POST",
url: 'Utils/Processor',
data: JSON.stringify(lData),
success:
function(lData,l2,l3)
{
var lResponseJSON=JSON.parse(lData)
},
dataType: "text"
});
}
//////////////////////////
/////Monitor JS module
//////////////////////////
mGlobal.Monitor={};
mGlobal.Monitor.ScreenshotModal={};
mGlobal.Monitor.GenerateUniqueID=function(inPrefix="tempUID=") {
return inPrefix+Math.round(Math.random()*1000)+"-"+Math.round(Math.random()*10000)+"-"+Math.round(Math.random()*1000)
}
//inHostURI: http://localhost:8081
mGlobal.Monitor.ScreenshotModal.Show=function(inHostURI=" ") {
$('.ui.modal.daemon-screenshot').modal('show');
//Функция обновления картинки
lScreenshotUpdate=function() {
lScreenshotSrc=inHostURI+"/GetScreenshot?"+mGlobal.Monitor.GenerateUniqueID()
$(".daemon-screenshot img").attr('src', lScreenshotSrc);
}
mGlobal.Monitor.ScreenshotModal.timerId=setInterval(lScreenshotUpdate,1500)
}
mGlobal.Monitor.ScreenshotModal.Close=function() {
clearInterval(mGlobal.Monitor.ScreenshotModal.timerId);
}
///Monitor
mGlobal.Monitor.DaemonList={}
mGlobal.Monitor.DaemonList.fRefreshTable=function() {
///Загрузка данных
$.ajax({
type: "GET",
url: 'Monitor/JSONDaemonListGet',
data: '',
success:
function(lData,l2,l3)
{
var lResponseJSON=JSON.parse(lData)
///Сформировать HTML код новой таблицы
lHTMLCode=mGlobal.GeneralGenerateHTMLCodeHandlebars(".openrpa-hidden-monitor-table-general",lResponseJSON)
///Очистить дерево
//mGlobal.ElementTree.fClear();
///Прогрузить новую таблицу
$(".openrpa-monitor").html(lHTMLCode)
},
dataType: "text"
});
}
////////////////////////////////
///////Control panel
///////////////////////////////
///Refresh control panel
mGlobal.Monitor.fControlPanelRefresh=function() {
///Загрузка данных
$.ajax({
type: "GET",
url: 'Monitor/ControlPanelDictGet',
data: '',
success:
function(lData,l2,l3)
{
var lResponseJSON=JSON.parse(lData)
///Escape onclick
/// RenderRobotList
lResponseJSON["RenderRobotList"].forEach(
function(lItem){
if ('FooterButtonX2List' in lItem) {
/// FooterButtonX2List
lItem["FooterButtonX2List"].forEach(
function(lItem2){
if ('OnClick' in lItem) {
lOnClickEscaped = lItem["OnClick"];
lOnClickEscaped = lOnClickEscaped.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
lItem["OnClick"] = lOnClickEscaped;
}
}
);
/// FooterButtonX1List
lItem["FooterButtonX1List"].forEach(
function(lItem2){
if ('OnClick' in lItem) {
lOnClickEscaped = lItem["OnClick"];
lOnClickEscaped = lOnClickEscaped.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
lItem["OnClick"] = lOnClickEscaped;
}
}
);
}
}
);
///Сформировать HTML код новой таблицы
lHTMLCode=mGlobal.GeneralGenerateHTMLCodeHandlebars(".openrpa-hidden-control-panel",lResponseJSON)
//Присвоить ответ в mGlobal.Monitor.mResponseList
mGlobal.Monitor.mResponseList = lResponseJSON
///Set result in mGlobal.DataStorage
lResponseJSON["RenderRobotList"].forEach(
function(lItem){
if ('DataStorageKey' in lItem) {
mGlobal["DataStorage"][lItem['DataStorageKey']]=lItem
}
}
)
///Очистить дерево
//mGlobal.ElementTree.fClear();
///Прогрузить новую таблицу
$(".openrpa-control-panel").html(lHTMLCode)
},
dataType: "text"
});
}
///
mGlobal.Monitor.mControlPanelAutoUpdateSeconds=3;
mGlobal.Monitor.mControlPanelAutoUpdateSecondsCurrent=3;
mGlobal.Monitor.fControlPanelAutoUpdateRun=function(inRefreshSeconds) {
mGlobal.Monitor.mControlPanelAutoUpdateSeconds=inRefreshSeconds;
//Функция обновления текста кнопки обновления
lControlPanelUpdate=function() {
mGlobal.Monitor.mControlPanelAutoUpdateSecondsCurrent=mGlobal.Monitor.mControlPanelAutoUpdateSecondsCurrent-1
if (mGlobal.Monitor.mControlPanelAutoUpdateSecondsCurrent==-1) {
mGlobal.Monitor.mControlPanelAutoUpdateSecondsCurrent=mGlobal.Monitor.mControlPanelAutoUpdateSecondsCurrent=mGlobal.Monitor.mControlPanelAutoUpdateSeconds;
mGlobal.Monitor.fControlPanelRefresh()
}
$(".openrpa-control-panel-general .openrpa-refresh-button").html("Refresh "+mGlobal.Monitor.mControlPanelAutoUpdateSecondsCurrent);
}
mGlobal.Monitor.mControlPanelAutoUpdateTimerId=setInterval(lControlPanelUpdate,1000)
}
mGlobal.Monitor.fControlPanelRefresh()
mGlobal.Monitor.fControlPanelAutoUpdateRun(3);
////////////////////////////////
/////// /Orchestrator/RobotRDPActive/ControlPanelDictGet
///////////////////////////////
mGlobal.RobotRDPActive = {}
///Refresh control panel
mGlobal.RobotRDPActive.fControlPanelRefresh=function() {
///Загрузка данных
$.ajax({
type: "GET",
url: 'Orchestrator/RobotRDPActive/ControlPanelDictGet',
data: '',
success:
function(lData,l2,l3)
{
var lResponseJSON=JSON.parse(lData)
///Сформировать HTML код новой таблицы
lHTMLCode=mGlobal.GeneralGenerateHTMLCodeHandlebars(".openrpa-hidden-robotrdpactive-control-panel",lResponseJSON)
//Присвоить ответ в mGlobal.RobotRDPActive.mResponseList
mGlobal.RobotRDPActive.mResponseList = lResponseJSON
///Прогрузить новую таблицу
$(".openrpa-robotrdpactive-control-panel").html(lHTMLCode)
},
dataType: "text"
});
}
///
mGlobal.RobotRDPActive.mControlPanelAutoUpdateSeconds=3;
mGlobal.RobotRDPActive.mControlPanelAutoUpdateSecondsCurrent=3;
mGlobal.RobotRDPActive.fControlPanelAutoUpdateRun=function(inRefreshSeconds) {
mGlobal.RobotRDPActive.mControlPanelAutoUpdateSeconds=inRefreshSeconds;
//Функция обновления текста кнопки обновления
lControlPanelUpdate=function() {
mGlobal.RobotRDPActive.mControlPanelAutoUpdateSecondsCurrent=mGlobal.RobotRDPActive.mControlPanelAutoUpdateSecondsCurrent-1
if (mGlobal.RobotRDPActive.mControlPanelAutoUpdateSecondsCurrent==-1) {
mGlobal.RobotRDPActive.mControlPanelAutoUpdateSecondsCurrent=mGlobal.RobotRDPActive.mControlPanelAutoUpdateSecondsCurrent=mGlobal.RobotRDPActive.mControlPanelAutoUpdateSeconds;
mGlobal.RobotRDPActive.fControlPanelRefresh()
}
$(".openrpa-robotrdpactive-control-panel-general .openrpa-refresh-button").html("Refresh "+mGlobal.RobotRDPActive.mControlPanelAutoUpdateSecondsCurrent);
}
mGlobal.RobotRDPActive.mControlPanelAutoUpdateTimerId=setInterval(lControlPanelUpdate,1000)
}
mGlobal.RobotRDPActive.fControlPanelRefresh()
mGlobal.RobotRDPActive.fControlPanelAutoUpdateRun(3);
mGlobal.Test=function() {
///Обнулить таблицу
lData = [
{
"Type":"GlobalDictKeyListValueSet",
"key_list":["Storage","Robot_R01"],
"value":{
"RunDateTimeString":"Test1",
"StepCurrentName":"Test2",
"StepCurrentDuration":"Test3"
}
}
]
$.ajax({
type: "POST",
url: 'Utils/Processor',
data: JSON.stringify(lData),
success:
function(lData,l2,l3)
{
//console.log(lData)
},
dataType: "text"
});
}
///////////////////////////////
///Processor functions
///////////////////////////////
mGlobal.Processor = {}
mGlobal.Processor.ServerValueAppend = function(inKeyList,inValue) {
lData = [
{
"Type":"GlobalDictKeyListValueAppend",
"KeyList": inKeyList,
"Value": inValue
}
]
///Обнулить таблицу
$('.ui.modal.basic .content').html("");
$.ajax({
type: "POST",
url: 'Utils/Processor',
data: JSON.stringify(lData),
success:
function(lData,l2,l3)
{
var lResponseJSON=JSON.parse(lData)
///TODO Show error if exist error
},
dataType: "text"
});
}
mGlobal.Processor.ServerValueSet = function(inKeyList,inValue) {
lData = [
{
"Type":"GlobalDictKeyListValueSet",
"KeyList": inKeyList,
"Value": inValue
}
]
///Обнулить таблицу
$('.ui.modal.basic .content').html("");
$.ajax({
type: "POST",
url: 'Utils/Processor',
data: JSON.stringify(lData),
success:
function(lData,l2,l3)
{
var lResponseJSON=JSON.parse(lData)
///TODO Show error if exist error
},
dataType: "text"
});
}
mGlobal.Processor.ServerValueOperatorPlus = function(inKeyList,inValue) {
lData = [
{
"Type":"GlobalDictKeyListValueOperator+",
"KeyList": inKeyList,
"Value": inValue
}
]
///Обнулить таблицу
$('.ui.modal.basic .content').html("");
$.ajax({
type: "POST",
url: 'Utils/Processor',
data: JSON.stringify(lData),
success:
function(lData,l2,l3)
{
var lResponseJSON=JSON.parse(lData)
///TODO Show error if exist error
},
dataType: "text"
});
}
mGlobal.Processor.Send = function(inData) {
lData = inData
$.ajax({
type: "POST",
url: 'Utils/Processor',
data: JSON.stringify(lData),
success:
function(lData,l2,l3)
{
var lResponseJSON=JSON.parse(lData)
///TODO Show error if exist error
},
dataType: "text"
});
}
mGlobal.Server= {}
mGlobal.Server.JSONGet=function(inMethod, inURL, inDataJSON, inCallback)
{
$.ajax({
type: inMethod,
url: inURL,
data: JSON.stringify(inDataJSON),
success:
function(lData,l2,l3)
{
var lResponseJSON=JSON.parse(lData)
inCallback(lResponseJSON)
},
dataType: "text"
});
}
/////////////////
///Modal
///////////////////
mGlobal.Modal={}
/////////////////////////////////////////////////////
mGlobal.Modal.TableFilter={}
mGlobal.Modal.TableFilter.Show=function(inJSON) {
//{
// "Title":"",
// "Headers":["Header1","Header2"],
// "Rows": [["Cell1","Cell2"],["Cell2-1","Cell2-2"]],
// "FilterOnKeyUp": "<JS Code>" //Fill here in function
//}
//Set js handler to Search field
inJSON["FilterOnKeyUp"]="mGlobal.Modal.TableFilter.FilterUpdate(this.value);"
///Set value
mGlobal.Modal.TableFilter.mDataJSON = inJSON
//Render HTML
lHTMLCode=mGlobal.GeneralGenerateHTMLCodeHandlebars(".openrpa-handlebar-template-table-filter",inJSON);
///Установить HTML код
$('.ui.modal.basic .content').html(lHTMLCode);
$('.ui.modal.basic').modal('show');
//DO widest modal for table with scroll x
$("div.ui.basic.modal.transition.visible.active.scrolling")[0].style["width"]="1300px"
$("div.ui.basic.modal.transition.visible.active.scrolling")[0].style["overflow"]="scroll"
}
//Service function
mGlobal.Modal.TableFilter.FilterUpdate=function(inFilterValue) {
//Get JSON, apply filter, clone data
lDataJSON = clone(mGlobal.Modal.TableFilter.mDataJSON)
delete lDataJSON["Rows"]
lDataJSON["Rows"]=[]
//Filter code [any occurence in the row is ok for push! ]
mGlobal.Modal.TableFilter.mDataJSON["Rows"].forEach(
function(inElement) {
lFlagElementAppend = false
inElement.forEach(
function(inElement2) {
if (String(inElement2).includes(inFilterValue)) {
lFlagElementAppend = true
}
}
)
if (lFlagElementAppend) {
lDataJSON["Rows"].push(inElement)
}
}
)
//Clear Filter Title property (fixed in html)
delete lDataJSON["FilterOnKeyUp"]
delete lDataJSON["Title"]
//Search the table element [replace only table html]
lElement = $('.ui.modals.active .content table.table')[0]
lElementParentElement = lElement.parentNode
lElement.parentNode.removeChild(lElement);
//Render HTML
lHTMLCode=mGlobal.GeneralGenerateHTMLCodeHandlebars(".openrpa-handlebar-template-table-filter",lDataJSON);
///Установить HTML код
lElementParentElement.insertAdjacentHTML("beforeend",lHTMLCode);
}
/////////////////////////////////////////////////////////////
mGlobal.Modal.ListFilter={}
mGlobal.Modal.ListFilter.Show=function(inJSON) {
//{
// "Title":"",
// "List":[{"Header":"","Description":""}],
// "FilterOnKeyUp": "<JS Code>" //Fill here in function
//}
//Set js handler to Search field
inJSON["FilterOnKeyUp"]="mGlobal.Modal.ListFilter.FilterUpdate(this.value);"
///Set value
mGlobal.Modal.ListFilter.mDataJSON = inJSON
//Render HTML
lHTMLCode=mGlobal.GeneralGenerateHTMLCodeHandlebars(".openrpa-handlebar-template-list-filter",inJSON);
///Установить HTML код
$('.ui.modal.basic .content').html(lHTMLCode);
$('.ui.modal.basic').modal('show');
}
//Service function
mGlobal.Modal.ListFilter.FilterUpdate=function(inFilterValue) {
//Get JSON, apply filter, clone data
lDataJSON = clone(mGlobal.Modal.ListFilter.mDataJSON)
delete lDataJSON["List"]
lDataJSON["List"]=[]
//Filter code [any occurence in the row is ok for push! ]
mGlobal.Modal.ListFilter.mDataJSON["List"].forEach(
function(inElement) {
lFlagElementAppend = false
if (String(inElement["Header"]).includes(inFilterValue)) {
lFlagElementAppend = true
}
if (String(inElement["Description"]).includes(inFilterValue)) {
lFlagElementAppend = true
}
if (lFlagElementAppend) {
lDataJSON["List"].push(inElement)
}
}
)
//Clear Filter Title property (fixed in html)
delete lDataJSON["FilterOnKeyUp"]
delete lDataJSON["Title"]
//Search the table element [replace only table html]
lElement = $('.ui.modals.active .content div.ui.inverted.segment')[0]
lElementParentElement = lElement.parentNode
lElement.parentNode.removeChild(lElement);
//Render HTML
lHTMLCode=mGlobal.GeneralGenerateHTMLCodeHandlebars(".openrpa-handlebar-template-list-filter",lDataJSON);
///Установить HTML код
lElementParentElement.insertAdjacentHTML("beforeend",lHTMLCode);
}
mGlobal.UserRoleHierarchyDict = null // Put here the user role hierarchy
// UAC Ask
mGlobal.UserRoleAsk=function(inList) {
var lResult = true; // Init flag
var lRoleHierarchyDict = mGlobal.UserRoleHierarchyDict; // get the Hierarchy
// Try to get value from key list
var lKeyValue = lRoleHierarchyDict; // Init the base
var lListLength = inList.length;
for (var i = 0; i<lListLength; i++) {
var lItem = inList[i]; // get the item
if (typeof lKeyValue == "object") {
if (lItem in lKeyValue) { // Has key
lKeyValue = lKeyValue[lItem]; // Get the value and go to the next loop iteration
} else { // Else branch - true or false
if (Object.keys(lKeyValue).length > 0) { // false - if Dict has some elements
lResult = false; // Set the False Flag
} else {
lResult = true; // Set the true flag
}
break; // Stop the loop
}
} else { // Has element with no detalization - return true
lResult = true; // Set the flag
break; // Close the loop
}
}
return lResult; // Return the result
}
// Check user roles and update the Orchestrator UI
mGlobal.UserRoleUpdate=function() {
$.ajax({
type: "POST",
url: 'Orchestrator/UserRoleHierarchyGet',
data: "",
success:
function(lData,l2,l3)
{
var lUACAsk = mGlobal.UserRoleAsk // Alias
var lResponseDict=JSON.parse(lData)
mGlobal.UserRoleHierarchyDict = lResponseDict // set the user role hierarchy
//Turn on the Lookmachine screenshot button
if (lUACAsk(["Orchestrator","Controls","LookMachineScreenshots"])) {
$(".openrpa-control-lookmachinescreenshot").show() //Show button
}
//Turn on the restart orchestrator button
if (lUACAsk(["Orchestrator","Controls","RestartOrchestrator"])) {
$(".openrpa-control-restartorchestrator").show() //Show button
}
//Turn on the rdp session list
if (lUACAsk(["Orchestrator","RDPActive","ListRead"])) {
$(".openrpa-rdpactive-title").show() //Show section
$(".openrpa-robotrdpactive-control-panel-general").show() //Show section
}
},
dataType: "text"
});
}
mGlobal.UserRoleUpdate() // Cal the update User Roles function
});

@ -9,662 +9,7 @@
crossorigin="anonymous"></script>
<script src="3rdParty/Semantic-UI-CSS-master/semantic.min.js"></script>
<script src="3rdParty/Handlebars/handlebars-v4.1.2.js"></script>
<script>
var mGlobal={}
$(document)
.ready(function() {
// fix main menu to page on passing
$('.main.menu').visibility({
type: 'fixed'
});
$('.overlay').visibility({
type: 'fixed',
offset: 80
});
// lazy load images
$('.image').visibility({
type: 'image',
transition: 'vertical flip in',
duration: 500
});
// show dropdown on hover
$('.main.menu .ui.dropdown').dropdown({
on: 'hover'
});
function clone(obj) {
var copy;
// Handle the 3 simple types, and null or undefined
if (null == obj || "object" != typeof obj) return obj;
// Handle Date
if (obj instanceof Date) {
copy = new Date();
copy.setTime(obj.getTime());
return copy;
}
// Handle Array
if (obj instanceof Array) {
copy = [];
for (var i = 0, len = obj.length; i < len; i++) {
copy[i] = clone(obj[i]);
}
return copy;
}
// Handle Object
if (obj instanceof Object) {
copy = {};
for (var attr in obj) {
if (obj.hasOwnProperty(attr)) copy[attr] = clone(obj[attr]);
}
return copy;
}
throw new Error("Unable to copy obj! Its type isn't supported.");
}
//For data storage key
mGlobal["DataStorage"] = {}
String.prototype.replaceAll = function(search, replace){
return this.split(search).join(replace);
}
mGlobal.GeneralGenerateHTMLCodeHandlebars=function(inInnerTemplateSelector,inData) {
lHTMLTemplate=$(inInnerTemplateSelector)[0].innerHTML
//console.log(lHTMLTemplate)
//Компиляция
var template = Handlebars.compile(lHTMLTemplate);
//Вставка данных
var lHTMLResult = template(inData);
return lHTMLResult
}
mGlobal.GeneralGenerateHTMLCode=function(inTemplateHTMLSelector,inItemDictionary,inKeywordPrefix="::",inKeywordPostfix="::") {
///Получить заготовку
lTemplateHTMLCode=$(inTemplateHTMLSelector)[0].outerHTML
///Определить ключь экранирования специальных ключевых слов
///Выполнить циклические замены, если там есть пожходящие ключи
lResultHTMLCode=lTemplateHTMLCode
for(var lKey in inItemDictionary) {
lHTMLKey=inKeywordPrefix+lKey+inKeywordPostfix;
lResultHTMLCode=lResultHTMLCode.replaceAll(lHTMLKey,inItemDictionary[lKey])
}
///Вернуть результат
return lResultHTMLCode
}
//////////////////////////
/////Info JS module
//////////////////////////
mGlobal.Info={};
mGlobal.Info.TableActivityLogScheduleListRefresh=function() {
}
//////////////////////////
/////Controller JS module
//////////////////////////
mGlobal.Controller={};
mGlobal.Controller.CMDRunText=function(inCMDText) {
///Подготовить конфигурацию
lData = [
{"Type":"CMDStart", "Command": inCMDText}
]
///Обнулить таблицу
$.ajax({
type: "POST",
url: 'Utils/Processor',
data: JSON.stringify(lData),
success:
function(lData,l2,l3){},
dataType: "text"
});
}
mGlobal.Controller.CMDRun=function() {
///Обнулить таблицу
lCMDCode=$(".openrpa-controller-cmd-run-input")[0].value
///Подготовить конфигурацию
lData = [
{"Type":"CMDStart", "Command": lCMDCode }
]
$.ajax({
type: "POST",
url: 'Utils/Processor',
data: JSON.stringify(lData),
success:
function(lData,l2,l3)
{
var lResponseJSON=JSON.parse(lData)
///Отправить запрос на формирование таблицы
//lHTMLCode=console.log("CMDRun result: "+lResponseJSON[0].result)
},
dataType: "text"
});
}
mGlobal.Controller.CMDRunGUILogout=function() {
///Обнулить таблицу
lCMDCode="for /f \"skip=1 tokens=2\" %s in ('query user %USERNAME%') do (tscon \\dest:console)"
//lCMDCode = lCMDCode.replace(/\\n/g, "\\n")
// .replace(/\\'/g, "\\'")
// .replace(/\\"/g, '\\"')
// .replace(/\\&/g, "\\&")
// .replace(/\\r/g, "\\r")
// .replace(/\\t/g, "\\t")
// .replace(/\\b/g, "\\b")
// .replace(/\\f/g, "\\f")
// .replace('"', "\\\"");
///Подготовить конфигурацию
lData = [
{"Type":"CMDStart", "Command": lCMDCode }
]
$.ajax({
type: "POST",
url: 'Utils/Processor',
data: JSON.stringify(lData),
success:
function(lData,l2,l3)
{
var lResponseJSON=JSON.parse(lData)
///Отправить запрос на формирование таблицы
//lHTMLCode=console.log("CMDRun result: "+lResponseJSON[0].result)
},
dataType: "text"
});
}
///Перезагрузить Orchestrator
mGlobal.Controller.OrchestratorRestart=function() {
///Подготовить конфигурацию
lData = [
{"Type":"OrchestratorRestart"}
]
$.ajax({
type: "POST",
url: 'Utils/Processor',
data: JSON.stringify(lData),
success:
function(lData,l2,l3)
{
var lResponseJSON=JSON.parse(lData)
},
dataType: "text"
});
}
//////////////////////////
/////Monitor JS module
//////////////////////////
mGlobal.Monitor={};
mGlobal.Monitor.ScreenshotModal={};
mGlobal.Monitor.GenerateUniqueID=function(inPrefix="tempUID=") {
return inPrefix+Math.round(Math.random()*1000)+"-"+Math.round(Math.random()*10000)+"-"+Math.round(Math.random()*1000)
}
//inHostURI: http://localhost:8081
mGlobal.Monitor.ScreenshotModal.Show=function(inHostURI=" ") {
$('.ui.modal.daemon-screenshot').modal('show');
//Функция обновления картинки
lScreenshotUpdate=function() {
lScreenshotSrc=inHostURI+"/GetScreenshot?"+mGlobal.Monitor.GenerateUniqueID()
$(".daemon-screenshot img").attr('src', lScreenshotSrc);
}
mGlobal.Monitor.ScreenshotModal.timerId=setInterval(lScreenshotUpdate,1500)
}
mGlobal.Monitor.ScreenshotModal.Close=function() {
clearInterval(mGlobal.Monitor.ScreenshotModal.timerId);
}
///Monitor
mGlobal.Monitor.DaemonList={}
mGlobal.Monitor.DaemonList.fRefreshTable=function() {
///Загрузка данных
$.ajax({
type: "GET",
url: 'Monitor/JSONDaemonListGet',
data: '',
success:
function(lData,l2,l3)
{
var lResponseJSON=JSON.parse(lData)
///Сформировать HTML код новой таблицы
lHTMLCode=mGlobal.GeneralGenerateHTMLCodeHandlebars(".openrpa-hidden-monitor-table-general",lResponseJSON)
///Очистить дерево
//mGlobal.ElementTree.fClear();
///Прогрузить новую таблицу
$(".openrpa-monitor").html(lHTMLCode)
},
dataType: "text"
});
}
////////////////////////////////
///////Control panel
///////////////////////////////
///Refresh control panel
mGlobal.Monitor.fControlPanelRefresh=function() {
///Загрузка данных
$.ajax({
type: "GET",
url: 'Monitor/ControlPanelDictGet',
data: '',
success:
function(lData,l2,l3)
{
var lResponseJSON=JSON.parse(lData)
///Escape onclick
/// RenderRobotList
lResponseJSON["RenderRobotList"].forEach(
function(lItem){
if ('FooterButtonX2List' in lItem) {
/// FooterButtonX2List
lItem["FooterButtonX2List"].forEach(
function(lItem2){
if ('OnClick' in lItem) {
lOnClickEscaped = lItem["OnClick"];
lOnClickEscaped = lOnClickEscaped.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
lItem["OnClick"] = lOnClickEscaped;
}
}
);
/// FooterButtonX1List
lItem["FooterButtonX1List"].forEach(
function(lItem2){
if ('OnClick' in lItem) {
lOnClickEscaped = lItem["OnClick"];
lOnClickEscaped = lOnClickEscaped.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
lItem["OnClick"] = lOnClickEscaped;
}
}
);
}
}
);
///Сформировать HTML код новой таблицы
lHTMLCode=mGlobal.GeneralGenerateHTMLCodeHandlebars(".openrpa-hidden-control-panel",lResponseJSON)
//Присвоить ответ в mGlobal.Monitor.mResponseList
mGlobal.Monitor.mResponseList = lResponseJSON
///Set result in mGlobal.DataStorage
lResponseJSON["RenderRobotList"].forEach(
function(lItem){
if ('DataStorageKey' in lItem) {
mGlobal["DataStorage"][lItem['DataStorageKey']]=lItem
}
}
)
///Очистить дерево
//mGlobal.ElementTree.fClear();
///Прогрузить новую таблицу
$(".openrpa-control-panel").html(lHTMLCode)
},
dataType: "text"
});
}
///
mGlobal.Monitor.mControlPanelAutoUpdateSeconds=3;
mGlobal.Monitor.mControlPanelAutoUpdateSecondsCurrent=3;
mGlobal.Monitor.fControlPanelAutoUpdateRun=function(inRefreshSeconds) {
mGlobal.Monitor.mControlPanelAutoUpdateSeconds=inRefreshSeconds;
//Функция обновления текста кнопки обновления
lControlPanelUpdate=function() {
mGlobal.Monitor.mControlPanelAutoUpdateSecondsCurrent=mGlobal.Monitor.mControlPanelAutoUpdateSecondsCurrent-1
if (mGlobal.Monitor.mControlPanelAutoUpdateSecondsCurrent==-1) {
mGlobal.Monitor.mControlPanelAutoUpdateSecondsCurrent=mGlobal.Monitor.mControlPanelAutoUpdateSecondsCurrent=mGlobal.Monitor.mControlPanelAutoUpdateSeconds;
mGlobal.Monitor.fControlPanelRefresh()
}
$(".openrpa-control-panel-general .openrpa-refresh-button").html("Refresh "+mGlobal.Monitor.mControlPanelAutoUpdateSecondsCurrent);
}
mGlobal.Monitor.mControlPanelAutoUpdateTimerId=setInterval(lControlPanelUpdate,1000)
}
mGlobal.Monitor.fControlPanelRefresh()
mGlobal.Monitor.fControlPanelAutoUpdateRun(3);
////////////////////////////////
/////// /Orchestrator/RobotRDPActive/ControlPanelDictGet
///////////////////////////////
mGlobal.RobotRDPActive = {}
///Refresh control panel
mGlobal.RobotRDPActive.fControlPanelRefresh=function() {
///Загрузка данных
$.ajax({
type: "GET",
url: 'Orchestrator/RobotRDPActive/ControlPanelDictGet',
data: '',
success:
function(lData,l2,l3)
{
var lResponseJSON=JSON.parse(lData)
///Сформировать HTML код новой таблицы
lHTMLCode=mGlobal.GeneralGenerateHTMLCodeHandlebars(".openrpa-hidden-robotrdpactive-control-panel",lResponseJSON)
//Присвоить ответ в mGlobal.RobotRDPActive.mResponseList
mGlobal.RobotRDPActive.mResponseList = lResponseJSON
///Прогрузить новую таблицу
$(".openrpa-robotrdpactive-control-panel").html(lHTMLCode)
},
dataType: "text"
});
}
///
mGlobal.RobotRDPActive.mControlPanelAutoUpdateSeconds=3;
mGlobal.RobotRDPActive.mControlPanelAutoUpdateSecondsCurrent=3;
mGlobal.RobotRDPActive.fControlPanelAutoUpdateRun=function(inRefreshSeconds) {
mGlobal.RobotRDPActive.mControlPanelAutoUpdateSeconds=inRefreshSeconds;
//Функция обновления текста кнопки обновления
lControlPanelUpdate=function() {
mGlobal.RobotRDPActive.mControlPanelAutoUpdateSecondsCurrent=mGlobal.RobotRDPActive.mControlPanelAutoUpdateSecondsCurrent-1
if (mGlobal.RobotRDPActive.mControlPanelAutoUpdateSecondsCurrent==-1) {
mGlobal.RobotRDPActive.mControlPanelAutoUpdateSecondsCurrent=mGlobal.RobotRDPActive.mControlPanelAutoUpdateSecondsCurrent=mGlobal.RobotRDPActive.mControlPanelAutoUpdateSeconds;
mGlobal.RobotRDPActive.fControlPanelRefresh()
}
$(".openrpa-robotrdpactive-control-panel-general .openrpa-refresh-button").html("Refresh "+mGlobal.RobotRDPActive.mControlPanelAutoUpdateSecondsCurrent);
}
mGlobal.RobotRDPActive.mControlPanelAutoUpdateTimerId=setInterval(lControlPanelUpdate,1000)
}
mGlobal.RobotRDPActive.fControlPanelRefresh()
mGlobal.RobotRDPActive.fControlPanelAutoUpdateRun(3);
mGlobal.Test=function() {
///Обнулить таблицу
lData = [
{
"Type":"GlobalDictKeyListValueSet",
"key_list":["Storage","Robot_R01"],
"value":{
"RunDateTimeString":"Test1",
"StepCurrentName":"Test2",
"StepCurrentDuration":"Test3"
}
}
]
$.ajax({
type: "POST",
url: 'Utils/Processor',
data: JSON.stringify(lData),
success:
function(lData,l2,l3)
{
//console.log(lData)
},
dataType: "text"
});
}
///////////////////////////////
///Processor functions
///////////////////////////////
mGlobal.Processor = {}
mGlobal.Processor.ServerValueAppend = function(inKeyList,inValue) {
lData = [
{
"Type":"GlobalDictKeyListValueAppend",
"KeyList": inKeyList,
"Value": inValue
}
]
///Обнулить таблицу
$('.ui.modal.basic .content').html("");
$.ajax({
type: "POST",
url: 'Utils/Processor',
data: JSON.stringify(lData),
success:
function(lData,l2,l3)
{
var lResponseJSON=JSON.parse(lData)
///TODO Show error if exist error
},
dataType: "text"
});
}
mGlobal.Processor.ServerValueSet = function(inKeyList,inValue) {
lData = [
{
"Type":"GlobalDictKeyListValueSet",
"KeyList": inKeyList,
"Value": inValue
}
]
///Обнулить таблицу
$('.ui.modal.basic .content').html("");
$.ajax({
type: "POST",
url: 'Utils/Processor',
data: JSON.stringify(lData),
success:
function(lData,l2,l3)
{
var lResponseJSON=JSON.parse(lData)
///TODO Show error if exist error
},
dataType: "text"
});
}
mGlobal.Processor.ServerValueOperatorPlus = function(inKeyList,inValue) {
lData = [
{
"Type":"GlobalDictKeyListValueOperator+",
"KeyList": inKeyList,
"Value": inValue
}
]
///Обнулить таблицу
$('.ui.modal.basic .content').html("");
$.ajax({
type: "POST",
url: 'Utils/Processor',
data: JSON.stringify(lData),
success:
function(lData,l2,l3)
{
var lResponseJSON=JSON.parse(lData)
///TODO Show error if exist error
},
dataType: "text"
});
}
mGlobal.Processor.Send = function(inData) {
lData = inData
$.ajax({
type: "POST",
url: 'Utils/Processor',
data: JSON.stringify(lData),
success:
function(lData,l2,l3)
{
var lResponseJSON=JSON.parse(lData)
///TODO Show error if exist error
},
dataType: "text"
});
}
mGlobal.Server= {}
mGlobal.Server.JSONGet=function(inMethod, inURL, inDataJSON, inCallback)
{
$.ajax({
type: inMethod,
url: inURL,
data: JSON.stringify(inDataJSON),
success:
function(lData,l2,l3)
{
var lResponseJSON=JSON.parse(lData)
inCallback(lResponseJSON)
},
dataType: "text"
});
}
/////////////////
///Modal
///////////////////
mGlobal.Modal={}
/////////////////////////////////////////////////////
mGlobal.Modal.TableFilter={}
mGlobal.Modal.TableFilter.Show=function(inJSON) {
//{
// "Title":"",
// "Headers":["Header1","Header2"],
// "Rows": [["Cell1","Cell2"],["Cell2-1","Cell2-2"]],
// "FilterOnKeyUp": "<JS Code>" //Fill here in function
//}
//Set js handler to Search field
inJSON["FilterOnKeyUp"]="mGlobal.Modal.TableFilter.FilterUpdate(this.value);"
///Set value
mGlobal.Modal.TableFilter.mDataJSON = inJSON
//Render HTML
lHTMLCode=mGlobal.GeneralGenerateHTMLCodeHandlebars(".openrpa-handlebar-template-table-filter",inJSON);
///Установить HTML код
$('.ui.modal.basic .content').html(lHTMLCode);
$('.ui.modal.basic').modal('show');
//DO widest modal for table with scroll x
$("div.ui.basic.modal.transition.visible.active.scrolling")[0].style["width"]="1300px"
$("div.ui.basic.modal.transition.visible.active.scrolling")[0].style["overflow"]="scroll"
}
//Service function
mGlobal.Modal.TableFilter.FilterUpdate=function(inFilterValue) {
//Get JSON, apply filter, clone data
lDataJSON = clone(mGlobal.Modal.TableFilter.mDataJSON)
delete lDataJSON["Rows"]
lDataJSON["Rows"]=[]
//Filter code [any occurence in the row is ok for push! ]
mGlobal.Modal.TableFilter.mDataJSON["Rows"].forEach(
function(inElement) {
lFlagElementAppend = false
inElement.forEach(
function(inElement2) {
if (String(inElement2).includes(inFilterValue)) {
lFlagElementAppend = true
}
}
)
if (lFlagElementAppend) {
lDataJSON["Rows"].push(inElement)
}
}
)
//Clear Filter Title property (fixed in html)
delete lDataJSON["FilterOnKeyUp"]
delete lDataJSON["Title"]
//Search the table element [replace only table html]
lElement = $('.ui.modals.active .content table.table')[0]
lElementParentElement = lElement.parentNode
lElement.parentNode.removeChild(lElement);
//Render HTML
lHTMLCode=mGlobal.GeneralGenerateHTMLCodeHandlebars(".openrpa-handlebar-template-table-filter",lDataJSON);
///Установить HTML код
lElementParentElement.insertAdjacentHTML("beforeend",lHTMLCode);
}
/////////////////////////////////////////////////////////////
mGlobal.Modal.ListFilter={}
mGlobal.Modal.ListFilter.Show=function(inJSON) {
//{
// "Title":"",
// "List":[{"Header":"","Description":""}],
// "FilterOnKeyUp": "<JS Code>" //Fill here in function
//}
//Set js handler to Search field
inJSON["FilterOnKeyUp"]="mGlobal.Modal.ListFilter.FilterUpdate(this.value);"
///Set value
mGlobal.Modal.ListFilter.mDataJSON = inJSON
//Render HTML
lHTMLCode=mGlobal.GeneralGenerateHTMLCodeHandlebars(".openrpa-handlebar-template-list-filter",inJSON);
///Установить HTML код
$('.ui.modal.basic .content').html(lHTMLCode);
$('.ui.modal.basic').modal('show');
}
//Service function
mGlobal.Modal.ListFilter.FilterUpdate=function(inFilterValue) {
//Get JSON, apply filter, clone data
lDataJSON = clone(mGlobal.Modal.ListFilter.mDataJSON)
delete lDataJSON["List"]
lDataJSON["List"]=[]
//Filter code [any occurence in the row is ok for push! ]
mGlobal.Modal.ListFilter.mDataJSON["List"].forEach(
function(inElement) {
lFlagElementAppend = false
if (String(inElement["Header"]).includes(inFilterValue)) {
lFlagElementAppend = true
}
if (String(inElement["Description"]).includes(inFilterValue)) {
lFlagElementAppend = true
}
if (lFlagElementAppend) {
lDataJSON["List"].push(inElement)
}
}
)
//Clear Filter Title property (fixed in html)
delete lDataJSON["FilterOnKeyUp"]
delete lDataJSON["Title"]
//Search the table element [replace only table html]
lElement = $('.ui.modals.active .content div.ui.inverted.segment')[0]
lElementParentElement = lElement.parentNode
lElement.parentNode.removeChild(lElement);
//Render HTML
lHTMLCode=mGlobal.GeneralGenerateHTMLCodeHandlebars(".openrpa-handlebar-template-list-filter",lDataJSON);
///Установить HTML код
lElementParentElement.insertAdjacentHTML("beforeend",lHTMLCode);
}
mGlobal.UserRoleHierarchyDict = null // Put here the user role hierarchy
// UAC Ask
mGlobal.UserRoleAsk=function(inList) {
var lResult = true; // Init flag
var lRoleHierarchyDict = mGlobal.UserRoleHierarchyDict; // get the Hierarchy
// Try to get value from key list
var lKeyValue = lRoleHierarchyDict; // Init the base
var lListLength = inList.length;
for (var i = 0; i<lListLength; i++) {
var lItem = inList[i]; // get the item
if (typeof lKeyValue == "object") {
if (lItem in lKeyValue) { // Has key
lKeyValue = lKeyValue[lItem]; // Get the value and go to the next loop iteration
} else { // Else branch - true or false
if (Object.keys(lKeyValue).length > 0) { // false - if Dict has some elements
lResult = false; // Set the False Flag
} else {
lResult = true; // Set the true flag
}
break; // Stop the loop
}
} else { // Has element with no detalization - return true
lResult = true; // Set the flag
break; // Close the loop
}
}
return lResult; // Return the result
}
// Check user roles and update the Orchestrator UI
mGlobal.UserRoleUpdate=function() {
$.ajax({
type: "POST",
url: 'Orchestrator/UserRoleHierarchyGet',
data: "",
success:
function(lData,l2,l3)
{
var lUACAsk = mGlobal.UserRoleAsk // Alias
var lResponseDict=JSON.parse(lData)
mGlobal.UserRoleHierarchyDict = lResponseDict // set the user role hierarchy
//Turn on the Lookmachine screenshot button
if (lUACAsk(["Orchestrator","Controls","LookMachineScreenshots"])) {
$(".openrpa-control-lookmachinescreenshot").show() //Show button
}
//Turn on the restart orchestrator button
if (lUACAsk(["Orchestrator","Controls","RestartOrchestrator"])) {
$(".openrpa-control-restartorchestrator").show() //Show button
}
//Turn on the rdp session list
if (lUACAsk(["Orchestrator","RDPActive","ListRead"])) {
$(".openrpa-rdpactive-title").show() //Show section
$(".openrpa-robotrdpactive-control-panel-general").show() //Show section
}
},
dataType: "text"
});
}
mGlobal.UserRoleUpdate() // Cal the update User Roles function
})
;
</script>
<script src = "Index.js"></script>
<style type="text/css">
body {
@ -705,16 +50,17 @@
<body>
<div class="ui internally celled grid">
<div class="row black">
<div class="three wide column">
<h1 class="ui header inverted">OpenRPA</h1>
</div>
<div class="eleven wide column">
<div class="sixteen wide column" style="display: flex;">
<img src="pyOpenRPA_logo.png" width="70px;" height="70px"></img>
&nbsp;&nbsp;&nbsp;
<h1 class="ui header inverted" style="cursor: pointer" onclick="window.open('https://gitlab.com/UnicodeLabs/OpenRPA','_blank');">pyOpenRPA</h1>
<h5 style="cursor: pointer" onclick="window.open('https://www.facebook.com/RU.IT4Business','_blank');">by Ivan Maslov</h5>
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
<h1 class="ui header inverted">Orchestrator Web GUI</h1>
</div>
<div class="two wide column">
<h5>by Ivan Maslov</h5>
</div>
</div>
<div class="row">
<div class="sixteen wide column openrpa-control-panel-general" >
@ -774,29 +120,39 @@
</script>
</div>
</div>
<div class="row black">
<div class="five wide column">
<h2 class="ui header inverted">Controls</h2>
</div>
<div class="two wide column">
<h2 class="ui header inverted">...</h2>
</div>
<div class="nine wide column">
<h2 class="ui header inverted openrpa-rdpactive-title" style="display:none;">Robot RDP active list</h2>
</div>
</div>
<div class="row">
<div class="five wide column">
<button class="ui labeled icon button openrpa-control-lookmachinescreenshot" onclick="mGlobal.Monitor.ScreenshotModal.Show();" style="display: none;">
<i class="desktop icon"></i>
Look machine screenshot
</button>
<button class="ui labeled icon button red openrpa-control-restartorchestrator" onclick="mGlobal.Controller.OrchestratorRestart(); " style="display: none;>
<i class="redo icon"></i>
Restart Orchestrator
</button>
<h2 class="ui header">
<i class="settings icon"></i>
<div class="content">
Administration
</div>
</h2>
<div class="ui animated button openrpa-control-lookmachinescreenshot huge green" onclick="mGlobal.Monitor.ScreenshotModal.Show();" style="display: none; margin-top: 5px;">
<div class="visible content">Show live screenshots</div>
<div class="hidden content">
<i class="right arrow icon"></i>
</div>
</div>
<div class="ui animated button openrpa-control-restartorchestrator yellow" onclick="mGlobal.Controller.OrchestratorRestart();" style="display: none; margin-top: 5px;">
<div class="visible content">Restart Orchestrator</div>
<div class="hidden content">
<i class="right arrow icon"></i>
</div>
</div>
<div class="ui animated button openrpa-control-restartorchestrator" onclick="mGlobal.Controller.OrchestratorRestart();" style="display: none; margin-top: 5px;">
<div class="visible content">Git pull + restart Orchestrator (next release)</div>
<div class="hidden content">
<i class="right arrow icon"></i>
</div>
</div>
<div class="ui animated button openrpa-control-restartorchestrator red" onclick="mGlobal.Controller.OrchestratorRestart();" style="display: none; margin-top: 5px;">
<div class="visible content">Restart PC (next release)</div>
<div class="hidden content">
<i class="right arrow icon"></i>
</div>
</div>
<script class="openrpa-hidden-monitor-table-general" style="display:none" type="text/x-handlebars-template">
<table class="ui celled table">
<thead>
@ -874,6 +230,12 @@
<div class="two wide column">
</div>
<div class="nine wide column openrpa-robotrdpactive-control-panel-general" style="display:none;">
<h2 class="ui header openrpa-rdpactive-title" style="display:none;">
<i class="server icon"></i>
<div class="content">
RDP active list
</div>
</h2>
<div class="ui info message">
<button class="ui icon button labeled " onclick="mGlobal.RobotRDPActive.fControlPanelRefresh();">
<i class="sync alternate icon"></i>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

@ -67,6 +67,9 @@ class testHTTPServer_RequestHandler(BaseHTTPRequestHandler):
#Мост между файлом и http запросом (новый формат)
if self.path == '/favicon.ico':
self.SendResponseContentTypeFile('image/x-icon', os.path.join(lStudioFolder, "Web\\favicon.ico"))
#Мост между файлом и http запросом (новый формат)
if self.path == '/pyOpenRPA_logo.png':
self.SendResponseContentTypeFile('image/png', os.path.join(lStudioFolder, "..\\Resources\\Web\\pyOpenRPA_logo.png"))
# POST
def do_POST(self):
#Restart studio

@ -856,12 +856,23 @@
<body>
<div class="ui internally celled grid">
<div class="row black">
<div class="three wide column">
<h1>Studio</h1>
<div class="sixteen wide column" style="display: flex;">
<img src="pyOpenRPA_logo.png" width="70px;" height="70px"></img>
&nbsp;&nbsp;&nbsp;
<h1 class="ui header inverted" style="cursor: pointer" onclick="window.open('https://gitlab.com/UnicodeLabs/OpenRPA','_blank');">pyOpenRPA</h1>
<h5 style="cursor: pointer" onclick="window.open('https://www.facebook.com/RU.IT4Business','_blank');">by Ivan Maslov</h5>
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
<h1 class="ui header inverted">Studio Web GUI</h1>
</div>
<div class="eleven wide column">
<div class="ui input">
</div>
<div class="row">
<div class="six wide column" >
<div class="ui input">
<input class="openrpa-value-backend" type="text" placeholder="Backend win32 | uia" value="win32">
</div>
<button class="ui labeled icon button red" onclick="mGlobal.Actions.fRestartStudioServer();">
@ -870,12 +881,8 @@
</button>
<b style="font-size:10px;color: #b1b1b1;" >Backend: win32 or uia</b>
</div>
<div class="two wide column">
<h2 class="ui header inverted" style="margin-bottom:5px;">OpenRPA</h2>
<h5 style="margin-top:5px; cursor: pointer" onclick="window.open('http://UnicodeLabs.ru','_blank');">by UnicodeLabs</h5>
</div>
</div>
<div class="row">
<div class="six wide column rpa-object-tree" >

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 110 KiB

@ -0,0 +1,18 @@
Dear RPA-tors. Let me congratulate you with great change in the RPA world. **The first enterprise level open source RPA platform is here!**
> The OpenRPA - free, fast and reliable
The **OpenRPA** is based on Python and using well known opensource solutions such as Selenium, OpenCV, Win32, UI automation and others. Thanks to it we were able to create consolidated platform with all possible features.
The **OpenRPA** is distributed under the MIT license which allows you to use it in any way you want and any time you need without any restrictions.
At the time of this writing the OpenRPA is successefully using in several big Russian companies. Companies in which it was decided to develop own RPA division with no dependencies on expensive licenses.
The **OpenRPA** consist of:
- WinPython 3.7.1 32-bit & 64-bit, license MIT (https://github.com/winpython/winpython)
- Selenium v..., license Apache 2.0
- pywinauto 0.6.5, license BSD 3-Clause (https://github.com/pywinauto/pywinauto)
- Semantic UI ..., license MIT (https://github.com/Semantic-Org/Semantic-UI)
- PyAutoGUI ..., license BSD 3-Clause (https://github.com/asweigart/pyautogui)
- keyboard ..., license MIT (https://github.com/boppreh/keyboard)
- OpenCV ...
- pywin32

@ -0,0 +1,41 @@
Here you can find information about the OpenRPA modules.
## The OpenRPA has 3 main tools:
- Studio
- Robot
- Orchestrator
## Description
`Studio`
___
Studio tool has been developed to help RPA-tors to create the robot algorythms.<br>
___
**Features**
- Run actions
- Create visual algorythms of the robot
- Desktop app: Analyze desktop app ui tree
- Desktop app: Search desktop app ui by mouse
- Desktop app: Generate & edit the UIO Selector
___
`Robot`
___
The Robot tool is the core of any action execution in OpenRPA. All action from algorythms are perfoming by the Robot tool. It looks like a console process without graphic user interface.
___
**Features**
- Based on Python (killer feature)
- Support Win32 GUI framework (desktop app)
- Support UI automation framework (desktop app)
- Support Selenium (web app)
- Support PyAutoGUI (screen capture & mouse)
- Support OpenCV (computer vision)
___
`Orchestrator`
___
The Orchestrator tool has been developed to maintain robot infrastructure (2+ robots algorythm).
___
**Features**
- Start/Stop robot algorythm
- Robot scheduler
- Remote machine screenshot viewer
- Remote machine cmd shell
- Remote machine logs storage
___

@ -0,0 +1,21 @@
Are you ready to install the OpenRPA solution on your machine?<br>
Ok, we start. <br>
**Do the following operations:**
- Download the OpenRPA package from master branch on GitLab [Download ZIP](https://gitlab.com/UnicodeLabs/OpenRPA/-/archive/master/OpenRPA-master.zip)
- Unzip the package
**Installation has been completed :)**
## How to check installation
- Run portable python (built in the OpenRPA)
- x32 python (OpenRPA\Resources\WPy32-3720\python-3.7.2\python.exe)
- x64 python (OpenRPA\Resources\WPy64-3720\python-3.7.2.amd64\python.exe)
**The OpenRPA has been successfully installed if the portable python 3.7.2 was started without any exceptions (see screenshot).**
![image](uploads/cb5dec8cecafa7d64f6cd14b2672acce/image.png)
## System requirements
- OS Windows 7+
- Need Windows package KB2999226 if use windows Vista/7/8/8.1/Server 2008/Server 2012 [Download package](https://support.microsoft.com/ru-ru/help/2999226)
- For OpenCV: OS Windows 7/8/8/10 only (no Windows Server)

@ -0,0 +1,60 @@
# Content
- [How to run](#how-to-run)
- [UI Description](#ui-description)
- [How to extract UI tree](#how-to-extract-ui-tree)
- [How to search UI object by mouse hover](#how-to-search-ui-object-by-mouse-hover)
- [How to extract UI object properties](#how-to-extract-ui-object-properties)
# How to run
- For OS x32
- Run (double click): OpenRPA\Studio\StudioRun_32.cmd (for OS x32)
- For OS x64
- Run (double click): OpenRPA\Studio\StudioRun_64.cmd (for OS x64)
- Wait text "running server" in console. Default browser will be open automatically
- **Attention!** The studio tool does not support the Internet explorer (any version) for GUI rendering (lol)
![image](uploads/504b98f76747f63900a2943532a946bb/image.png)
# UI Description
**The studio tool GUI contains of:**
- 1. UI tree viewer
- 2. Selected UI object hierarchy list
- 3. Selected UI object property list
- 4. UIO selector editor
- 5. UIO action panel
- 6. Another Python activity panel
- 7. Action/activity list
*Look it on the GUI screenshots are listed below*
## GUI Screenshot 1
![image](uploads/d3d6ad14a7e50843bd89d2b14a092fee/image.png)
## GUI Screenshot 2
![image](uploads/65b7d51c0a5b21e6b27dc23d4062d3ca/image.png)
# How to extract UI tree
In order to extract the UI tree do the following: in `UI tree viewer` choose the object you are interested and click the button "Expand".
## Action: Click the button "Expand"
![image](uploads/6effc376ff6ea928840674bd744caced/image.png)
## Result
![image](uploads/18b9ab36126c8c32168bf5bbb9330701/image.png)
# How to search UI object by mouse hover
In order to search UI object do the following: in `UI tree viewer` choose the parent object, where you are want to search UI object, and click the button "Mouse search". The mouse search mode will start. Turn mouse on the UI object you are interested and wait when the studio will highlight the UI object. After the hightlight hold the "Ctrl" key and wait 3 seconds. The interested UI object will be shown in `UI tree viewer`.
## Action: Click the button "Mouse search"
![image](uploads/84d7e4de0c840631f87cf7b325b53ad8/image.png)
## Action: Turn mouse on the UI object you are interested and hold the "Ctrl" key for 3 seconds
![image](uploads/b7c38d622bf5b7afa5b26d1686d7302a/image.png)
## Result: The interested UI object will be shown in `UI tree viewer`
![image](uploads/629bbf4db2c01b9c640194230949fcd2/image.png)
# How to extract UI object properties
In order to extract UI object properties do the following: in `Selected UI object hierarchy list` choose the UI object you are interested and click it. The UI object property list will be shown in `Selected UI object property list`
## Action: Choose the UI object you are interested and click it
![image](uploads/2c27be5bdde20b5d062cbb40e74eaec5/image.png)
## Result: The UI object property list will be shown in `Selected UI object property list`
![image](uploads/f235ae29099a713e0246cd574ac3a17c/image.png)

@ -0,0 +1,67 @@
# Content
- [About](#about)
- [How to use](#how-to-use)
- [Create python script](#create-python-script)
- [Execute python script](#execute-python-script)
# About
The Robot tool is the main module for production process automation. It has no graphic/console interface. All low-level actions to OS are perfoming by the Robot tool in OpenRPA.
# How to use
You can use the robot by the several ways:
- In Python script
- In Studio script (n/a)
## Create python script
In order to use robot just add Robot tool folder in work directory and add line "import GUI" in your script.
### Example
> import sys <br>
> sys.path.append('../../')<br>
> import selenium [#Web app access](https://gitlab.com/UnicodeLabs/OpenRPA/wikis/05.1.-Theory-&-practice:-Web-app-access-(Chrome,-Firefox,-Opera))<br>
> import GUI [#Win32 & UI Automation access](https://gitlab.com/UnicodeLabs/OpenRPA/wikis/05.2.-Theory-&-practice:-Desktop-app-UI-access-(win32-and-UI-automation-dlls)) <br>
> import pyautogui [#Screen capture/recognition](https://gitlab.com/UnicodeLabs/OpenRPA/wikis/05.4.-Theory-&-practice:-Screen-capture-&-image-recognition) [#Mouse manipulation](https://gitlab.com/UnicodeLabs/OpenRPA/wikis/05.3.-Theory-&-practice:-Keyboard-&-mouse-manipulation)<br>
> import cv2 [#Computer vision](https://gitlab.com/UnicodeLabs/OpenRPA/wikis/05.4.-Theory-&-practice:-Screen-capture-&-image-recognition)<br>
> import keyboard [#Keyboard manipulation](https://gitlab.com/UnicodeLabs/OpenRPA/wikis/05.3.-Theory-&-practice:-Keyboard-&-mouse-manipulation)<br>
## Execute python script
The OpenRPA is fully portable solution. It contains own python enviroment both 32 and 64 bit versions. So, you can execute your python script in several ways:
- Execute in python x32 (\OpenRPA\Resources\WPy32-3720\python-3.7.2)
- Execute in python x64 (\OpenRPA\Resources\WPy64-3720\python-3.7.2.amd64)
- Execute from .cmd file
### Execute in the Python x32
To execute your python script in x32 bit version just write in command line from x32 python directory: <br>
> cd "\OpenRPA\Resources\WPy32-3720\python-3.7.2"<br>
> python.exe "path to your python script.py"<br>
### Execute in the Python x64
To execute your python script in x32 bit version just write in command line from x32 python directory: <br>
> cd "\OpenRPA\Resources\WPy64-3720\python-3.7.2.amd64"<br>
> python.exe "path to your python script.py"<br>
### Execute from .cmd file
In order to simplify the execution process you can write several code lines in file with the .cmd extansion: <br>
> cd %~dp0
> copy /Y ..\Resources\WPy32-3720\python-3.7.2\python.exe ..\Resources\WPy32-3720\python-3.7.2\OpenRPAOrchestrator.exe
> .\..\Resources\WPy32-3720\python-3.7.2\OpenRPAOrchestrator.exe orchestratorMain.py
> pause >nul
## Use in studio script (n/a)
> import sys <br>
> sys.path.append('../../')<br>
> import GUI<br>
> import keyboard<br>
> import subprocess<br>
> import time<br>
>
> #Highlight the UI Object in Folder explorer<br>
> GUI.UIOSelector_FocusHighlight([{"class_name":"CabinetWClass","backend":"uia"},{"ctrl_index":2},{"ctrl_index":0},{"ctrl_index":2},{"ctrl_index":0}])<br>
>
>#Wait 2 seconds<br>
>time.sleep(3)<br>
>
>#Loop: get child element of UI List<br>
>for lItem in GUI.UIOSelector_Get_UIO([{"class_name":"CabinetWClass","backend":"uia"},{"ctrl_index":2},{"ctrl_index":0},{"ctrl_index":2},{"ctrl_index":0}]).children():<br>
> &nbsp; &nbsp; &nbsp; &nbsp; print(str(lItem))<br>

@ -0,0 +1,22 @@
# Content
- About
- How to use
# About
The OpenRPA support web app manipulation (by the Selenium lib).
More docs about selenium you can find [here](https://selenium-python.readthedocs.io/)
# How to use
To start use selenium just import selenium modules in [the robot tool](https://gitlab.com/UnicodeLabs/OpenRPA/wikis/04.2.-Tool-Robot:-How-to-use). Here is the example of the usage.
> from selenium import webdriver <br>
> from selenium.webdriver.common.keys import Keys <br>
> <br>
> driver = webdriver.Chrome() <br>
> driver.get("http://www.python.org") <br>
> assert "Python" in driver.title <br>
> elem = driver.find_element_by_name("q") <br>
> elem.clear() <br>
> elem.send_keys("pycon") <br>
> elem.send_keys(Keys.RETURN) <br>
> assert "No results found." not in driver.page_source <br>
> driver.close() <br>

@ -0,0 +1,270 @@
Here you can find the docs and examples of the OpenRPA desktop (GUI) app access.
# Definitions
**UIO** - UI Object (class of pywinauto UI object) [pywinauto.base_wrapper]<br>
**UIOSelector** - List of dict (key attributes)<br>
**PWA** - PyWinAuto<br>
**PWASpecification** - List of dict (key attributes in pywinauto.find_window notation)<br>
**UIOTree** - Recursive Dict of Dict ... (UI Parent -> Child hierarchy)<br>
**UIOInfo** - Dict of UIO attributes<br>
**UIOActivity** - Activity of the UIO (UI object) from the Pywinauto module<br>
**UIOEI** - UI Object info object
# What is UIO?
UIO is a User Interface Object (pyOpenRPA terminology). For maximum compatibility, this instance is inherited from the object model developed in the [pywinauto library (click to get a list of available class functions)](https://pywinauto.readthedocs.io/en/latest/code/pywinauto.base_wrapper.html).
This approach allows us to implement useful functionality that has already been successfully developed in other libraries, and Supplement it with the missing functionality. In our case, the missing functionality is the ability to dynamically access UIO objects using UIO selectors.
# UIOSelector structure & example
<a name="UIOSelector_Structure_Examples"></a>
UIOSelector is the list of condition items for the UIO in GUI. Each item has condition attributes for detect applicable UIO. Here is the description of the available condition attributes in item.
**Desciption**<br>
```
[
{
"depth_start" :: [int, start from 1] :: the depth index, where to start check the condition list (default 1),
"depth_end" :: [int, start from 1] :: the depth index, where to stop check the condition list (default 1),
"ctrl_index" || "index" :: [int, starts from 0] :: the index of the UIO in parent UIO child list,
"title" :: [str] :: the condition for the UIO attribute *title*,
"title_re" :: [str] :: regular expression (python ver) for the condition for the UIO attribute *title*,
"rich_text" :: [str] :: the condition for the UIO attribute *rich_text*,
"rich_text_re" :: [str] :: regular expression (python ver) for the condition for the UIO attribute *rich_text*,
"class_name" :: [str] :: the condition for the UIO attribute *class_name*,
"class_name_re" :: [str] :: regular expression (python ver) for the condition for the UIO attribute *class_name*,
"friendly_class_name" :: [str] :: the condition for the UIO attribute *friendly_class_name*,
"friendly_class_name_re" :: [str] :: regular expression (python ver) for the condition for the UIO attribute *friendly_class_name*,
"control_type" :: [str] :: the condition for the UIO attribute *control_type*,
"control_type_re" :: [str] :: regular expression (python ver) for the condition for the UIO attribute *control_type*,
"is_enabled" :: [bool] :: the condition for the UIO attribute *is_enabled*. If UI object is enabled on GUI,
"is_visible" :: [bool] :: the condition for the UIO attribute *is_visible*. If UI object is visible on GUI,
"backend" :: [str, "win32" || "uia"] :: the method of UIO extraction (default "win32"). ATTENTION! Current option can be only for the first item of the UIO selector. For the next items this option will be implemented from the first item.
},
{ ... specification next level UIO }
]
```
**The UIO selector example**
```
[
{"class_name":"CalcFrame", "backend":"win32"}, # 1-st level UIO specification
{"title":"Hex", "depth_start":3, "depth_end": 3} # 3-rd level specification (because of attribute depth_start|depth_stop)
]
```
# The UIDesktop module (OpenRPA/Robot/UIDesktop.py)
The UIDesktop is extension of the pywinauto module which provide access to the desktop apps by the **win32** and **ui automation** dll frameworks (big thx to the Microsoft :) ).
## Functions
*Naming convention: \<InArgument\>\_\<ActivityName\>\_\<OutArgument - if exist>*<br>
### List
- [UIOSelector_Get_UIOList](#UIOSelector_Get_UIOList)
- [UIOSelector_Get_UIO](#UIOSelector_Get_UIO)
- [UIOSelector_Exist_Bool](#UIOSelector_Exist_Bool)
- [UIOSelectorsSecs_WaitAppear_List](#UIOSelectorsSecs_WaitAppear_List)
- [UIOSelectorsSecs_WaitDisappear_List](#UIOSelectorsSecs_WaitDisappear_List)
- [UIOSelectorSecs_WaitAppear_Bool](#UIOSelectorSecs_WaitAppear_Bool)
- [UIOSelectorSecs_WaitDisappear_Bool](#UIOSelectorSecs_WaitDisappear_Bool)
- [UIOSelector_Get_BitnessInt](#UIOSelector_Get_BitnessInt)
- [UIOSelector_SearchChildByMouse_UIO](#UIOSelector_SearchChildByMouse_UIO)
- [UIOSelector_SearchChildByMouse_UIOTree](#UIOSelector_SearchChildByMouse_UIOTree)
- [UIOSelector_Get_UIOInfoList](#UIOSelector_Get_UIOInfoList)
- [UIOSelector_IsExist_Bool](#UIOSelector_IsExist_Bool)
- [UIOSelector_WaitAppear_Dict](#UIOSelector_WaitAppear_Dict)
- [UIOSelector_TryRestore_Dict](#UIOSelector_TryRestore_Dict)
- [UIOSelector_Get_UIOActivityList](#UIOSelector_Get_UIOActivityList)
- [UIOSelectorUIOActivity_Run_Dict](#UIOSelectorUIOActivity_Run_Dict)
- [UIOSelector_Get_UIOInfo](#UIOSelector_Get_UIOInfo)
- [UIOSelector_GetChildList_UIOList](#UIOSelector_GetChildList_UIOList)
- [UIOSelector_SearchUIONormalize_UIOSelector](#UIOSelector_SearchUIONormalize_UIOSelector)
- [UIOSelector_SearchProcessNormalize_UIOSelector](#UIOSelector_SearchProcessNormalize_UIOSelector)
- [UIOSelector_FocusHighlight](#UIOSelector_FocusHighlight)
- [UIOSelector_Highlight](#UIOSelector_Highlight)
### Description
**`UIOSelector_Get_UIOList (inUIOSelector,inParentUIO=None,inFlagRaiseException=True)`**<br>
<a name="UIOSelector_Get_UIOList"></a>
___
Get the list of the UIO items<br>
___
**inUIOSelector** - UIOSelector - List of items, which contains condition attributes ([see UIOSelector Structure&Examples](#UIOSelector_Structure_Examples))<br>
**inParentUIO** - Technical argument. Applicable only if you has UIO object, which is parent for the futher search the child UIO.<br>
**inFlagRaiseException** - if True - raise exception if UIO hasn't been detected. False - don't raise the exception - return None.<br>
___
**`UIOSelector_Get_UIO (inUIOSelector,inParentUIO=None,inFlagRaiseException=True)`**<br>
<a name="UIOSelector_Get_UIO"></a>
___
Get first (if more than one UIO are applied) UIO (UI Object) <br>
___
**inUIOSelector** - UIOSelector - List of items, which contains condition attributes ([see UIOSelector Structure&Examples](#UIOSelector_Structure_Examples))<br>
**inParentUIO** - Technical argument. Applicable only if you has UIO object, which is parent for the futher search the child UIO.<br>
**inFlagRaiseException** - if True - raise exception if UIO hasn't been detected. False - don't raise the exception - return None.<br>
___
**`UIOSelector_Exist_Bool (inUIOSelector)`**<br>
<a name="UIOSelector_Exist_Bool"></a>
___
Check if UIO exist (Identified by the UIOSelector) <br>
___
**inUIOSelector** - UIOSelector - List of items, which contains condition attributes ([see UIOSelector Structure&Examples](#UIOSelector_Structure_Examples))<br>
___
**`UIOSelectorsSecs_WaitAppear_List (inUIOSelectorList,inWaitSecs,inFlagWaitAllInMoment=False)`**<br>
<a name="UIOSelectorsSecs_WaitAppear_List"></a>
___
Wait for UIO is appear (at least one of them or all at the same time). return: \[0,1,2\] - index of UIOSpecification, which is appear <br>
___
**inUIOSelectorList** - !ATTENTION! Current argument is not the UIOSelector. This is the !list! of the many UIO selectors. ([see UIOSelector Structure&Examples](#UIOSelector_Structure_Examples))<br>
**inWaitSecs** - Time in seconds to wait the UIO will appear.<br>
**inFlagWaitAllInMoment** - True - wait when UIO of the all UIOSelectors will appear. False - at least one UIO of the UIOSelector list.<br>
___
**`UIOSelectorsSecs_WaitDisappear_List (inUIOSelectorList,inWaitSecs,inFlagWaitAllInMoment=False)`**<br>
<a name="UIOSelectorsSecs_WaitDisappear_List"></a>
___
Wait for UIO is Disappear (at least one of them or all at the same time). return: \[0,1,2\] - index of UIOSpecification, which is Disappear <br>
___
**inUIOSelectorList** - !ATTENTION! Current argument is not the UIOSelector. This is the !list! of the many UIO selectors. ([see UIOSelector Structure&Examples](#UIOSelector_Structure_Examples))<br>
**inWaitSecs** - Time in seconds to wait the UIO will appear.<br>
**inFlagWaitAllInMoment** - True - wait when UIO of the all UIOSelectors will appear. False - at least one UIO of the UIOSelector list.<br>
___
**`UIOSelectorSecs_WaitAppear_Bool (inUIOSelector,inWaitSecs)`**<br>
<a name="UIOSelectorSecs_WaitAppear_Bool"></a>
___
Wait for UIO is appear (at least one of them or all at the same time). return: Bool - True - UIO is appear. <br>
___
**inUIOSelector** - UIOSelector - List of items, which contains condition attributes ([see UIOSelector Structure&Examples](#UIOSelector_Structure_Examples))<br>
**inWaitSecs** - Time in seconds to wait the UIO will appear.<br>
___
**`UIOSelectorSecs_WaitDisappear_Bool (inUIOSelector,inWaitSecs)`**<br>
<a name="UIOSelectorSecs_WaitDisappear_Bool"></a>
___
Wait for UIO is disappear (at least one of them or all at the same time) . return: Bool - True - UIO is Disappear. <br>
___
**inUIOSelector** - UIOSelector - List of items, which contains condition attributes ([see UIOSelector Structure&Examples](#UIOSelector_Structure_Examples))<br>
**inWaitSecs** - Time in seconds to wait the UIO will appear.<br>
___
**`UIOSelector_Get_BitnessInt (inUIOSelector)`**<br>
<a name="UIOSelector_Get_BitnessInt"></a>
___
Get process bitness (32 or 64). return None (if Process not found), int 32, or int 64<br>
___
**inUIOSelector** - UIOSelector - List of items, which contains condition attributes ([see UIOSelector Structure&Examples](#UIOSelector_Structure_Examples))<br>
___
**`UIOSelector_SearchChildByMouse_UIO(inUIOSelector)`**<br>
<a name="UIOSelector_SearchChildByMouse_UIO"></a>
___
Run the search UIO by mouse hover event. result = UIO element wrapper instance or None <br>
___
**inUIOSelector** - UIOSelector - List of items, which contains condition attributes ([see UIOSelector Structure&Examples](#UIOSelector_Structure_Examples))<br>
___
**`UIOSelector_SearchChildByMouse_UIOTree(inUIOSelector)`**<br>
<a name="UIOSelector_SearchChildByMouse_UIOTree"></a>
___
Run the search UIO by mouse hover event. result = UIO element wrapper instance or None <br>
___
**inUIOSelector** - UIOSelector - List of items, which contains condition attributes ([see UIOSelector Structure&Examples](#UIOSelector_Structure_Examples))<br>
___
**`UIOSelector_Get_UIOInfoList (inUIOSelector,inElement=None)`**<br>
<a name="UIOSelector_Get_UIOInfoList"></a>
___
Get the UIOEI object.<br>
___
**inUIOSelector** - UIOSelector - List of items, which contains condition attributes ([see UIOSelector Structure&Examples](#UIOSelector_Structure_Examples))<br>
___
**`UIOSelector_IsExist_Bool (inUIOSelector)`**<br>
<a name="UIOSelector_IsExist_Bool"></a>
___
Check is the UIO/UIO's by the UIOSelector exist <br>
___
**inUIOSelector** - UIOSelector - List of items, which contains condition attributes ([see UIOSelector Structure&Examples](#UIOSelector_Structure_Examples))<br>
___
**`UIOSelector_WaitAppear_Dict(inUIOSelector,inTimeout=60)`**<br>
<a name="UIOSelector_WaitAppear_Dict"></a>
___
Wait for the UIO by the UIOSelector appear <br>
___
**inUIOSelector** - UIOSelector - List of items, which contains condition attributes ([see UIOSelector Structure&Examples](#UIOSelector_Structure_Examples))<br>
___
**`UIOSelector_TryRestore_Dict(inUIOSelector)`**<br>
<a name="UIOSelector_TryRestore_Dict"></a>
___
Try to restore (maximize) window, if it's minimized. (!IMPORTANT! When use UIA framework minimized windows doesn't appear by the UIOSelector. You need to try restore windows and after that try to get UIO)<br>
___
**inUIOSelector** - UIOSelector - List of items, which contains condition attributes ([see UIOSelector Structure&Examples](#UIOSelector_Structure_Examples))<br>
___
**`UIOSelector_Get_UIOActivityList (inUIOSelector)`**<br>
<a name="UIOSelector_Get_UIOActivityList"></a>
___
Get the list of the UI object activities<br>
___
**inUIOSelector** - UIOSelector - List of items, which contains condition attributes ([see UIOSelector Structure&Examples](#UIOSelector_Structure_Examples))<br>
___
**`UIOSelectorUIOActivity_Run_Dict(inUIOSelector,inActionName,inArgumentList=\[\],inkwArgumentObject={})`**<br>
<a name="UIOSelectorUIOActivity_Run_Dict"></a>
___
Run the activity in UIO (UI Object) <br>
___
**inUIOSelector** - UIOSelector - List of items, which contains condition attributes ([see UIOSelector Structure&Examples](#UIOSelector_Structure_Examples))<br>
**inActionName** - UIOActivity (name) from Pywinauto<br>
___
**`UIOSelector_Get_UIOInfo(inUIOSelector)`**<br>
<a name="UIOSelector_Get_UIOInfo"></a>
___
Get the UIO dict of the attributes <br>
___
**inUIOSelector** - UIOSelector - List of items, which contains condition attributes ([see UIOSelector Structure&Examples](#UIOSelector_Structure_Examples))<br>
___
**`UIOSelector_GetChildList_UIOList(inUIOSelector=\[\],inBackend=mDefaultPywinautoBackend)`**<br>
<a name="UIOSelector_GetChildList_UIOList"></a>
___
Get list of child UIO's by Parent UIOSelector<br>
___
**inUIOSelector** - UIOSelector - List of items, which contains condition attributes ([see UIOSelector Structure&Examples](#UIOSelector_Structure_Examples))<br>
___
**`UIOSelector_SearchUIONormalize_UIOSelector (inUIOSelector)`**<br>
<a name="UIOSelector_SearchUIONormalize_UIOSelector"></a>
___
Technical def. Do UIOSelector normalization for the search processes.<br>
___
**inUIOSelector** - UIOSelector - List of items, which contains condition attributes ([see UIOSelector Structure&Examples](#UIOSelector_Structure_Examples))<br>
___
**`UIOSelector_SearchProcessNormalize_UIOSelector (inUIOSelector)`**<br>
<a name="UIOSelector_SearchProcessNormalize_UIOSelector"></a>
___
Technical def. Do UIOSelector normalization for the search processes.<br>
___
**inUIOSelector** - UIOSelector - List of items, which contains condition attributes ([see UIOSelector Structure&Examples](#UIOSelector_Structure_Examples))<br>
___
**`UIOSelector_FocusHighlight(inUIOSelector)`**<br>
<a name="UIOSelector_FocusHighlight"></a>
___
Set focus and highlight (draw outline) the element (in app) by the UIO selector.<br>
___
**inUIOSelector** - UIOSelector - List of items, which contains condition attributes ([see UIOSelector Structure&Examples](#UIOSelector_Structure_Examples))<br>
___
**`UIOSelector_Highlight(inUIOSelector)`**<br>
<a name="UIOSelector_Highlight"></a>
___
Highlight (draw outline) the element (in app) by the UIO selector.<br>
___
**inUIOSelector** - UIOSelector - List of items, which contains condition attributes ([see UIOSelector Structure&Examples](#UIOSelector_Structure_Examples))<br>
___

@ -0,0 +1 @@
**How to automate Keyboard and Mouse device in PC**

@ -0,0 +1,7 @@
**How to automate image recognition on PC**
Here you can find any ways you need to use in your business case:
* Find the exact match on the screen with the other image
* Use text recognition module (OCR)
* Use computer vision (CV) to identify the objects on screen/image/video
* Use artificial intelligence (AI) to make custom identification/classification/text recognition

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="208px" height="128px" viewBox="0 0 208 128" enable-background="new 0 0 208 128" xml:space="preserve">
<path fill="none" stroke="#FFFFFF" stroke-width="10" d="M15,5h178c5.523,0,10,4.477,10,10v98c0,5.523-4.477,10-10,10H15
c-5.523,0-10-4.477-10-10V15C5,9.477,9.477,5,15,5z"/>
<path fill="#FFFFFF" d="M30,98V30h20l20,25l20-25h20v68H90V59L70,84L50,59v39H30z M155,98l-30-33h20V30h20v35h20L155,98z"/>
</svg>

After

Width:  |  Height:  |  Size: 770 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

@ -0,0 +1,3 @@
cd %~dp0..\OpenRPA\Sources
..\Resources\WPy64-3720\python-3.7.2.amd64\python.exe "..\..\OpenRPA Habr\2. MonitoringModeProgrammer_Run_64.py"
pause >nul

@ -0,0 +1,17 @@
from pyOpenRPA.Robot import UIDesktop # Импорт модуля, который умеет управлять UI элеметами GUI приложений
import time # Библиотека сна алгоритма
import os # Билбиотека системных функций, позволит открять калькулятор, если это потребуется
lUIOSelectorCalculator = [{"title":"Калькулятор","class_name":"CalcFrame","backend":"win32"}] # Сформировали UIO селектор из студии pyOpenRPA
while True: # Вечный цикл
lExistBool = UIDesktop.UIOSelector_Exist_Bool(inUIOSelector=lUIOSelectorCalculator) # Проверить наличие окна по UIO селектору
if not lExistBool: # Проверить наличие окна калькулятора
os.system("calc") # Открыть калькулятор
else: # Проверить, что окно калькулятора не свернуто
lUIOCalculator = UIDesktop.UIOSelector_Get_UIO(inSpecificationList=lUIOSelectorCalculator) # Получить UIO экземпляр
if lUIOCalculator.is_minimized(): # Проверить, что калькулятор находится в свернутом виде
lUIOCalculator.restore() # Восстановить окно калькулятора из свернутого вида
else:
lCalcHex_IsExistBool = UIDesktop.UIOSelector_Exist_Bool(inUIOSelector=[{"class_name":"CalcFrame","backend":"win32"},{ "title":"Hex", "depth_start":3, "depth_end": 3}]) # Проверить наличие UI элемента по UIO селектору
if not lCalcHex_IsExistBool: # Проверить, что UI элемент отсутствует
lUIOCalculator.menu_select("&Вид -> &Программист") # Выполнить смену режима калькулятора
time.sleep(1) # Выполнить сон на 1 сек., после чего перейти на следующую итерацию

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

@ -0,0 +1,336 @@
Специально для Хабр я начинаю серию статей-туториалов по использованию RPA платформы [OpenRPA](https://habr.com/ru/post/506766/). Буду рад получить от вас комментарии и замечания, если возникнут какие-либо вопросы. Надеюсь, что эта история не оставит вас равнодушными.
![pyOpenRPA Туториал. Управление оконными GUI приложениями](https://habrastorage.org/webt/p8/_q/mo/p8_qmot0fkekqnwyqids33fthae.png)
Ранее я писал о том, что [OpenRPA](https://habr.com/ru/post/506766/) - это первая open source RPA платформа, которая позволяет польностью избавить себя от платных RPA аналогов. И, как выяснилось в процессе, эта тема позволяет не просто снять компании с "лицензионной иглы", а еще и увеличить получаемые бизнес-эффекты от разработанных роботов. Ведь архитектура новых RPA оказалась гораздо "легче" и, как следствие, быстрее.
Благодарю всех читателей, которые проявили интерес к моей [предыдущей статье](https://habr.com/ru/post/506766/) - я очень ценю мнение других, потому что именно это позволяет мне предлагать общественности наиболее актуальные решения. Еще раз спасибо вам за проявленный интерес!
В рамках этой статьи будет приведена подробная инструкция по разработке робота, который будет манипулировать оконными GUI приложениями.
*Под оконными приложениями понимаются все виды GUI приложений, которые не визуализируются в WEB браузерах.*
<cut/>
# Ремарка. OpenRPA теперь становится pyOpenRPA
С момента опубликования [предыдущей статьи](https://habr.com/ru/post/506766/) произошли небольшие изменения в названии RPA платформы, а именно: [OpenRPA](https://gitlab.com/UnicodeLabs/OpenRPA) переименовывается в [pyOpenRPA]([https://gitlab.com/UnicodeLabs/OpenRPA]).
<cut/>
## С чем это связано?
Дело в том, что само по себе название OpenRPA является "говорящим", и "лежит на поверхности". По этой причине его выбрал я, а через некоторое время и другие. Так как концепция [pyOpenRPA]([https://gitlab.com/UnicodeLabs/OpenRPA]) заключается в абсолютно безвоздмездном использовании и открытости для всех, в этой RPA платформе нет каких-либо бюджетов. В связи с этим нет возможности и отстаивать монопольное право на использование названия (да это и не нужно). В связи с этим, было принято решение немного скорректировать название, чтобы избавить пользователей от возможной путаницы.
По поводу [OpenRPA от другой команды](https://github.com/open-rpa/openrpa): очень надеюсь, что им удастся реализовать свою идею, превзойти по всем параметрам платные RPA платформы, и сохранить свою открытось. В мире open source мы не конкуренты, а коллеги, которые трудятся в одном и том же направлении - в направлении создания полезного открытого продукта. Если говорить про их RPA платформу, то там поставлена цель создания аналога коммерческой RPA платформы с визуальным программированием в основе. Идея очень интересная и привлекательная, но крайне трудозатратная по исполнению (ведь не просто так лучшие RPA платформы постоянно дорабатываются огромными командами разработчиков, что ведет к комерциализации проекта). Желаю им удачи в достижении поставленных целей.
<cut/>
# Навигация по статьям
Так как [pyOpenRPA](https://gitlab.com/UnicodeLabs/OpenRPA) - это достаточно крупная RPA платформа: туториал будет составлен в виде серий статей, в которых будут освещаться ключевые технологии. А уже освоив эти технологии, у вас появится возможность углубиться в то, что вам нужно.
**Ниже приведу планируемый перечень статей по этой тематике (для навигации):**
- [Отказываемся от платных RPA платформ и базируемся на OpenSource (pyOpenRPA)](https://habr.com/ru/post/506766/)
- \>> pyOpenRPA туториал. Управление оконными GUI приложениями
- pyOpenRPA туториал. Управление WEB приложениями (то, что мы смотрим в Chrome, Firefox, Opera)
- pyOpenRPA туториал. Управление клавиатурой & мышью
- pyOpenRPA туториал. Распознавание графических объектов на экране
<cut/>
# Немного теории и терминов
Давайте попробуем разобраться в том, как устроены GUI приложения, и почему мы можем ими управлять.
Начнем с простого. Рассмотрим на примере классического блокнота, что видим мы, и что видит робот.
<cut/>
## Что видим мы?
![Notepad_human](https://habrastorage.org/webt/gk/q_/ky/gkq_kyyivmojkiynqossxayojck.png)
## Что видит робот?
![Notepad_pyOpenRPA](https://habrastorage.org/webt/nj/sj/zx/njsjzxdzjzdimm_ushvz-f5eqaw.png)
<cut/>
## Интерпретация
Благодаря архитектуре современных операционных систем у сторонних программ имеется программная возможность по обращению к [UI элементам - они же UIO](https://ru.wikipedia.org/wiki/%D0%98%D0%BD%D1%82%D0%B5%D1%80%D1%84%D0%B5%D0%B9%D1%81_%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8F) сторонних GUI приложений. Эта возможность изначально разрабатывалась для того, чтобы позволить программистам проводить [регрессионное тестирование свеого софта](https://ru.wikipedia.org/wiki/%D0%A0%D0%B5%D0%B3%D1%80%D0%B5%D1%81%D1%81%D0%B8%D0%BE%D0%BD%D0%BD%D0%BE%D0%B5_%D1%82%D0%B5%D1%81%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5), но позже выяснилось, что эту возможность можно использовать и в бизнес-процессах компании.
Как мы видим на изображении выше, для робота, блокнот - это набор различных UIO. Причем не просто UIO, а UIO c **набором различных атрибутов, и набором различных действий**. И что самое главное - наш робот имеет полный доступ ко всем этим атрибутам и действиям.
> **Примеры атрибутов<br>**
> - hidden - элемент спрятан в GUI интерфейсе от глаз пользователя<br>
> - disabled - элемент недоступен для выполнения действий (нажатие, наведение мышкой и и т.д.)<br>
> **Примеры действий**
> - left click - клик левой кнопкой мыши<br>
> - right click - клик правой кнопкой мыши<br>
> - type text - ввод текста в активную область<br>
> - scroll up - пролистывание активной области вверх<br>
> - scroll down - пролистывание активной области вниз<br>
> - scroll left - пролистывание активной области влево<br>
> - scroll right - пролистывание активной области вправо<br>
Тут вы можете мне возразить, что это все ерунда, потому что в любой операционной системе реализованы алгоритмы, обеспечивающие разграничение информационных потоков, и одно приложение не может "залезть" в другое приложение. А я вам на это скажу, что это, действительно так, **но только для программного (технического) уровня**. Вся эта история с безопасностью не работает, когда речь заходит о доступе к GUI интерфейсам других приложений.
<cut/>
## Что такое UIO?
UIO - это User Interface Object (терминология pyOpenRPA). В целях обеспечения максимальной совместимости, этот экземпляр наследуется от обьектной модели, разработанной в библиотеке [pywinauto (нажми, чтобы получить список доступных функций класса)](https://pywinauto.readthedocs.io/en/latest/code/pywinauto.base_wrapper.html).
Данный подход позволяет имплементировать полезную функциональность, которая уже была успешно разработана в других бибилотеках, и дополнить ее недостающей функциональностью. В нашем случае, недостающей функциональностью является возможность динамического обращения к UIO обьектам по UIO селекторам.
<cut/>
## Правила формирования UIO селектора (UIOSelector)
UIO селектор - это список характеристических словарей (спецификаций UIO). Данные спецификации UIO содержат условия, с помощью которых библиотека pyOpenRPA определит UIO, удовлетворяющий условиям, заданным в спецификации UIO. Индекс спецификации UIO в списке UIO селектора харакетризует уровень вложенности целевого UIO.
Говоря другим языком, UIO селектор - это перечень условий, под которые может попасть 0, 1 или n UIO.
Ниже приведен перечень атрибутов - условий, которые можно использовать в спецификациях UIO:
```
[
{
"depth_start" :: [int, start from 1] :: глубина, с которой начинается поиск (по умолчанию 1),
"depth_end" :: [int, start from 1] :: глубина, до которой ведется поиск (по умолчанию 1),
"ctrl_index" || "index" :: [int, starts from 0] :: индекс UIO в списке у родительского UIO,
"title" :: [str] :: идентичное наименование атрибута *title* искомого объекта UIO,
"title_re" :: [str] :: регулярное выражение (python диалект) для отбора UIO, у которого атрибут *title* должен удовлетворять условию данного регулярного выражения,
"rich_text" :: [str] :: идентичное наименование атрибута *rich_text* искомого объекта UIO,
"rich_text_re" :: [str] :: регулярное выражение (python диалект) для отбора UIO, у которого атрибут *rich_text* должен удовлетворять условию данного регулярного выражения,
"class_name" :: [str] :: идентичное наименование атрибута *class_name* искомого объекта UIO,
"class_name_re" :: [str] :: регулярное выражение (python диалект) для отбора UIO, у которого атрибут *class_name* должен удовлетворять условию данного регулярного выражения,
"friendly_class_name" :: [str] :: идентичное наименование атрибута *friendly_class_name* искомого объекта UIO,
"friendly_class_name_re" :: [str] :: регулярное выражение (python диалект) для отбора UIO, у которого атрибут *friendly_class_name* должен удовлетворять условию данного регулярного выражения,
"control_type" :: [str] :: идентичное наименование атрибута *control_type* искомого объекта UIO,
"control_type_re" :: [str] :: регулярное выражение (python диалект) для отбора UIO, у которого атрибут *control_type* должен удовлетворять условию данного регулярного выражения,
"is_enabled" :: [bool] :: признак, что UIO доступен для выполнения действий,
"is_visible" :: [bool] :: признак, что UIO отображается на экране,
"backend" :: [str, "win32" || "uia"] :: вид способа адресации к UIO (по умолчанию "win32"). Внимание! Данный атрибут может быть указан только для первого элемента списка UIO селектора. Для остальных элементов списка данный атрибут будет проигнорирован.
},
{ ... спецификация UIO следующего уровня иерархии }
]
```
**Пример UIO селектора**
```
[
{"class_name":"CalcFrame", "backend":"win32"}, # Спецификация UIO 1-го уровня вложенности
{"title":"Hex", "depth_start":3, "depth_end": 3} # Спецификация UIO 1+3-го уровня вложенности (так как установлены атрибуты depth_start|depth_stop, определяющие глубину поиска UIO)
]
```
*PS. Перечень функций по работе с UIO селектором представлен в модуле UIDesktop (pyOpenRPA/Robot/UIDesktop.py). Именно эти функции будут использоваться при дальнейшей разработке робота.
Ознакомиться в полным перечнем функций модуля UIDesktop [можно здесь](https://gitlab.com/UnicodeLabs/OpenRPA/-/blob/master/Wiki/05.2.-Theory-&-practice.-Desktop-app-UI-access-(win32-and-UI-automation-dlls).md)*
<cut/>
# (По шагам) робот своими руками
Вот мы и добрались до самого интересного и важного раздела этого туториала - это пошаговый пример по созданию своего первого робота с использованием pyOpenRPA.
В качестве экспериментального робота поставим себе следующую цель: **Разработать робота, который будет контролировать вид интерфейса в приложении "Калькулятор"**. Если вид интерфейса будет отличаться от вида "Программист", то робот должен будет выставить данный вид в автоматическом режиме.
<cut/>
## Шаг 0. Подготовим интерпретатор Python 3 для нового робота (развернем pyOpenRPA)
В отличии от подавляющего большинства RPA платформ, в pyOpenRPA реализован другой подход подключения к проекту. Если в остальных RPA платформах необходимо писать робота на языке этой платформы (текстовый, или графический, или скриптовый язык), то в случае pyOpenRPA именно вы определяете где, как и когда нужно использовать эту библиотеку в проекте.
Доступно несколько вариантов загрузки [pyOpenRPA](https://gitlab.com/UnicodeLabs/OpenRPA):
- Вариант 1, простой. Скачать преднастроенную портативную версию с [GitLab страницы проекта](https://gitlab.com/UnicodeLabs/OpenRPA)
- Вариант 2, посложнее. Установить pyOpenRPA в свою версию интерпретатора Python 3 (pip install pyOpenRPA)
<cut/>
## Шаг 1. Создать проект робота
Для того, чтобы начать проект робота, необходимо создать папку проекта. В дальнейшем я затрону тему организации папок проектов для промышленных программных роботов. Но на текущий момент не буду заострять внимание на этом, чтобы сконцентрироваться непосредственно на основном - на логике работы с GUI окнами.
**Создадим следующую структуру проекта:**
- Папка "RobotCalc":
- Файл "RobotCalc_1.py" - скрипт робота 1, который мы пишем сейчас
- Файл "RobotCalc_1_Run_x64.cmd" - скрипт запуска робота 1
- Файл "RobotCalc_2.py" - скрипт робота 2, дополнение
- Файл "RobotCalc_2_Run_x64.cmd" - скрипт запуска робота 2
.cmd файлы в этом проекте играют важную роль - именно благодаря этим файлам мы будем иметь возможность выполнять запуск интересующего нас робота простым кликом по файлу.
**Ниже приведу пример "RobotCalc_1_Run_x64.cmd" файла (файл "RobotCalc_2_Run_x64.cmd" аналогичен):**
```
cd %~dp0
..\Resources\WPy64-3720\python-3.7.2.amd64\python.exe "RobotCalc_1.py"
pause >nul
```
<cut/>
## Шаг 2. Запустить студию pyOpenRPA и сформировать необходимые UIO селекторы
- Открыть калькулятор (win + r > calc > enter)
**Если вы скачали преднастроенную версию pyOpenRPA с GitLab (вариант 1, простой):**
- Выполнить запуск cmd файла web студии pyOpenRPA из репозитория "pyOpenRPA\Studio\pyOpenRPA.Studio_x64.cmd"
**Если вы скачали пакет pyOpenRPA с помощью pip install pyOpenRPA (вариант 2, посложнее):**
- Выполнить запуск интерпретатора python со следующими аргументами: python -m pyOpenRPA.Studio "..\Studio\SettingsStudioExample.py", где SettingsStudioExample.py - это конфигурационный файл запуска студии pyOpenRPA. Преднастроенный шаблон этого файла можно [скачать с репозитория pyOpenRPA в GitLab](https://gitlab.com/UnicodeLabs/OpenRPA/-/blob/master/Studio/SettingsStudioExample.py)
При любом из вариантов через 5 - 15 сек. должна автоматически отобразиться web студия pyOpenRPA (см. ниже)
![pyOpenRPA_studio](https://habrastorage.org/webt/zu/pm/v7/zupmv75pwn3nseyb3mdnuzwzrwa.png)
*Внешний вид web студии pyOpenRPA*
<cut/>
- В списке открытых оконных GUI приложений найти калькулятор и активировать режим поиска UI элемента по наведению указателя мыши (Кнопка "Mouse search")
- Переключиться на калькулятор (alt + tab)
- Навести указатель мыши на тот элемент, который нам необходим для того, чтобы определить состояние интерфейса калькулятора. Выберем radio кнопку Hex. Зеленая окантовка появляется поверх калькулятора благодаря студии pyOpenRPA - именно таким образом студия сообщает нам о том, какой UI элемент она видит в калькулятора по той точке, куда наведен указатель мыши.
![calc_radiobutton](https://habrastorage.org/webt/cm/rq/qs/cmrqqsomsdbcyez1kdo6_tzmcgm.png)
*Студии pyOpenRPA подсвечивает зеленой окантовкой обнаруженный UI элемент по месту указателя мыши на калькуляторе*
- Для того, чтобы остановить процесс поиска UI элемента необходимо зажать клавишу ctrl на 2-4 секунды, после чего в WEB интерфейсе студии появится иерархния до UI элемента, который студия подсвечивала зеленой окантовкой.
<cut/>
![pyOpenRPA_studio_calc_ui_hex](https://habrastorage.org/webt/vh/ai/tl/vhaitlupj8ntvxgj10rebcgp2ay.png)
*Студия pyOpenRPA отобразила иерархию нахождения UI элемента в калькуляторе после отправки сигнала завершения поиска UI элементов (длительное нажатие ctrl)*
- Для того, чтобы убедиться в том, что элемент был обнаружен корректно, достаточно нажать кнопку "Highlight" по тому UI элементу, который интересует. Программа повторно нарисует эеленую окантовку поверх того UI элемента, который был обнаружен.
- Далее выполнить клик по UI элементу в окне иерархии в студии, после чего перейти в окно редактирования UIO селектора (UIO селектор далее будет использоваться в коде робота в Python 3)
<cut/>
![pyOpenRPA_studio_calc_ui_hex_uio](https://habrastorage.org/webt/y2/_b/ck/y2_bckz1fb7c6z9x1gpyllqv2es.png)
*Студия pyOpenRPA сформировала UIO селектор в автоматическом режиме к UI элементу калькулятора*
- В нашем примере UI элемент расположен на 4-м уровне вложенности с атрибутом title = "Hex". В автоматическом режиме pyOpenRPA формирует UIO селектор по индексам расположения в вышестоящих UI элементах. Такой подход достаточно нежелательно использовать в конечных роботах, потому что индексы расположения UI элементов могут динамически изменяться во время работы программы.
- Произведем преобразование нашего UIO селектора:
```
[{"title":"Калькулятор","class_name":"CalcFrame","backend":"win32"},{"ctrl_index":0},{"ctrl_index":6},{"ctrl_index":1}]
```
в следующий вид:
```
[{"class_name":"CalcFrame","backend":"win32"},{ "title":"Hex", "depth_start":3, "depth_end": 3}]
```
- В результате преобразований убрали лишнее условие "title":"Калькулятор" и промежуточные уровни, которые характеризовались только индексами нахождения UI элементов. Вместо этого добавили условие поиска "title":"Hex" и установили область поиска "depth_start":3, "depth_end": 3 (в нашем случае это необходимо, потому что мы убрали явные уровни вложенности). Атрибуты "class_name" накладывает условие, что надо искать прилоежние с class_name = CalcFrame, а backend указывает pyOpenRPA, какую систему поиска UI элементов использовать (win32 или uia, у каждой и них есть + и -)
- С помощью кнопки "Hightlight element" убедимся в том, что UI элемент, по-прежнему, обнаруживается студией pyOpenRPA (при нажатии на кнопку поверх UI элемента должна быть отрисована зеленая окантовка - новый UIO селектор работает корректно)
<cut/>
- Данный UIO селектор будем использовать в роботе для проверки состояния интерфейса калькулятора: если UI элемент успешно обнаруживается, то режим калькулятора установлен верный. Если UI элемент не обнаруживается, то режим калькулятора установлен неверный, и его нужно будет изменить. Для того, чтобы проверить наличие UI элемента по UIO селектору воспользуемся функцией **pyOpenRPA.Robot.UIDesktop.UIOSelector_Exist_Bool**
```
lCalcHex_IsExistBool = UIDesktop.UIOSelector_Exist_Bool(inUIOSelector=[{"class_name":"CalcFrame","backend":"win32"},{ "title":"Hex", "depth_start":3, "depth_end": 3}]) # Проверить наличие UI элемента по UIO селектору
```
- Для того, чтобы установить режим программиста, воспользуемся еще одной возможностью win32 - активация события, расположенного в меню приложения (см. ниже).
![Calc Vid Programmist](https://habrastorage.org/webt/-u/u-/ss/-uu-ss0_0dp6bg5pnw8opmbay38.png)
*Вид "Программист" в калькуляторе*
Активация элемента меню выполняется с помощью специальной функции *menu_select* у корневого UIO объекта GUI приложения.
<cut/>
- С помощью студии pyOpenRPA сформируем UIO селектор корневого объекта калькулятора
```
lUIOSelectorCalculator = [{"title":"Калькулятор","class_name":"CalcFrame","backend":"win32"}] # Сформировали UIO селектор из студии pyOpenRPA
```
- Далее запросим UIO объект по UIO селектору, после чего вызовем функцию *menu_select*, в которую передадим строковый адрес вызываемого элемента меню
```
lUIOCalculator = UIDesktop.UIOSelector_Get_UIO(inSpecificationList=lUIOSelectorCalculator) # Получить UIO экземпляр
lUIOCalculator.menu_select("&Вид -> &Программист") # Выполнить смену режима калькулятора
```
<cut/>
## Шаг 3. Консолидируем код в проекте робота
Обадая всеми необходимыми UIO селекторами и функциями, перейдем к составлению целостного скрипта робота. Ниже я приведу код RobotCalc_1.py файла, готового для запуска (python.exe "RobotCalc_1.py") c детальным описанием каждой строки.
```
from pyOpenRPA.Robot import UIDesktop # Импорт модуля, который умеет управлять UI элеметами GUI приложений
import time # Библиотека сна алгоритма
import os # Библиотека системных функций, позволит открять калькулятор, если это потребуется
lUIOSelectorCalculator = [{"title":"Калькулятор","class_name":"CalcFrame","backend":"win32"}] # Сформировали UIO селектор из студии pyOpenRPA
while True: # Вечный цикл
lUIOCalculator = UIDesktop.UIOSelector_Get_UIO(inSpecificationList=lUIOSelectorCalculator) # Получить UIO экземпляр
lCalcHex_IsExistBool = UIDesktop.UIOSelector_Exist_Bool(inUIOSelector=[{"class_name":"CalcFrame","backend":"win32"},{ "title":"Hex", "depth_start":3, "depth_end": 3}]) # Проверить наличие UI элемента по UIO селектору
if not lCalcHex_IsExistBool: # Проверить, что UI элемент отсутствует
lUIOCalculator.menu_select("&Вид -> &Программист") # Выполнить смену режима калькулятора
time.sleep(1) # Выполнить сон на 1 сек., после чего перейти на следующую итерацию
```
Внимание! При запуске робота убедитесь в том, что калькулятор находится в активном состоянии на экране вашего компьютера. Робот начнет отслеживать состояние калькулятора. Если в калькуляторе не будет установлен режим программиста, то робот в течение 1 секунды вернет его в данный режим.
<cut/>
## Дополнение. Дорабатываем робота, чтобы он еще включал калькулятор (если он выключен), раскрывал его (если он свернут)
- Для решения поставленной задачи мы уже обладаем всеми необходимыми UIO селекторами. Необходимо только определиться с функциями, которые дополнительно будем использовать.
- Для запуска калькулятора будем использовать функцию *os.system*
```
os.system("calc") # Открыть калькулятор
```
<cut/>
- Для проверки состояния окна (свернуто в трэй или развернуто) *is_minimized*
```
lUIOCalculator.is_minimized()
```
- Для восстановления свернутого окна будем использовать функцию *restore*
```
lUIOCalculator.restore() # Восстановить окно калькулятора из свернутого вида
```
<cut/>
- Итого получим следующий исходный код робота (файл RobotCalc_2.py).
```
from pyOpenRPA.Robot import UIDesktop # Импорт модуля, который умеет управлять UI элеметами GUI приложений
import time # Библиотека сна алгоритма
import os # Билбиотека системных функций, позволит открять калькулятор, если это потребуется
lUIOSelectorCalculator = [{"title":"Калькулятор","class_name":"CalcFrame","backend":"win32"}] # Сформировали UIO селектор из студии pyOpenRPA
while True: # Вечный цикл
lExistBool = UIDesktop.UIOSelector_Exist_Bool(inUIOSelector=lUIOSelectorCalculator) # Проверить наличие окна по UIO селектору
if not lExistBool: # Проверить наличие окна калькулятора
os.system("calc") # Открыть калькулятор
else: # Проверить, что окно калькулятора не свернуто
lUIOCalculator = UIDesktop.UIOSelector_Get_UIO(inSpecificationList=lUIOSelectorCalculator) # Получить UIO экземпляр
if lUIOCalculator.is_minimized(): # Проверить, что калькулятор находится в свернутом виде
lUIOCalculator.restore() # Восстановить окно калькулятора из свернутого вида
else:
lCalcHex_IsExistBool = UIDesktop.UIOSelector_Exist_Bool(inUIOSelector=[{"class_name":"CalcFrame","backend":"win32"},{ "title":"Hex", "depth_start":3, "depth_end": 3}]) # Проверить наличие UI элемента по UIO селектору
if not lCalcHex_IsExistBool: # Проверить, что UI элемент отсутствует
lUIOCalculator.menu_select("&Вид -> &Программист") # Выполнить смену режима калькулятора
time.sleep(1) # Выполнить сон на 1 сек., после чего перейти на следующую итерацию
```
*PS 1. Для сравнения: Реализация аналогичного алгоритма в другой RPA платформе с помощью инструментов визуального программирования потребует в **3-4 раза больше пространства рабочей области экрана** (в связи со спецификой визуального программирования).*
*PS 2. Перечень всех функций в модуле UIDesktop (pyOpenRPA/Robot/UIDesktop.py)
Ознакомиться в полным перечнем функций модуля UIDesktop [можно здесь](https://gitlab.com/UnicodeLabs/OpenRPA/-/blob/master/Wiki/05.2.-Theory-&-practice.-Desktop-app-UI-access-(win32-and-UI-automation-dlls).md)*
<cut/>
# Подведем итоги
Итак, мы успешно преодолели первые шаги по созданию бесплатных программных роботов. Безусловно, эта статья покрывает далеко не все области программной роботизации. В следующих статьях-туториалах мы остановимся на оставшихся "столпах" роботизированного управления (мышь, клавиатура, распознавание изображения с экрана и web манипуляции).
Надеюсь, что рассмотренные технологии, в первую очередь, помогут вам или вашей компании в достижении поставленных целей. А во вторую очередь, пусть получают выгоду и другие участники RPA рынка (да, я про вендоров платных RPA платформ, многие из которых базируются в США).
Всегда открыт к вашим комментариям, и буду рад помочь вам в решении ваших вопросов.
До скорых публикаций!

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

@ -0,0 +1,336 @@
Специально для Хабр я начинаю серию статей-туториалов по использованию RPA платформы [OpenRPA](https://habr.com/ru/post/506766/). Буду рад получить от вас комментарии и замечания, если возникнут какие-либо вопросы. Надеюсь, что эта история не оставит вас равнодушными.
![pyOpenRPA Туториал. Управление оконными GUI приложениями](https://habrastorage.org/webt/p8/_q/mo/p8_qmot0fkekqnwyqids33fthae.png)
Ранее я писал о том, что [OpenRPA](https://habr.com/ru/post/506766/) - это первая open source RPA платформа, которая позволяет польностью избавить себя от платных RPA аналогов. И, как выяснилось в процессе, эта тема позволяет не просто снять компании с "лицензионной иглы", а еще и увеличить получаемые бизнес-эффекты от разработанных роботов. Ведь архитектура новых RPA оказалась гораздо "легче" и, как следствие, быстрее.
Благодарю всех читателей, которые проявили интерес к моей [предыдущей статье](https://habr.com/ru/post/506766/) - я очень ценю мнение других, потому что именно это позволяет мне предлагать общественности наиболее актуальные решения. Еще раз спасибо вам за проявленный интерес!
В рамках этой статьи будет приведена подробная инструкция по разработке робота, который будет манипулировать оконными GUI приложениями.
*Под оконными приложениями понимаются все виды GUI приложений, которые не визуализируются в WEB браузерах.*
<cut/>
# Ремарка. OpenRPA теперь становится pyOpenRPA
С момента опубликования [предыдущей статьи](https://habr.com/ru/post/506766/) произошли небольшие изменения в названии RPA платформы, а именно: [OpenRPA](https://gitlab.com/UnicodeLabs/OpenRPA) переименовывается в [pyOpenRPA]([https://gitlab.com/UnicodeLabs/OpenRPA]).
<cut/>
## С чем это связано?
Дело в том, что само по себе название OpenRPA является "говорящим", и "лежит на поверхности". По этой причине его выбрал я, а через некоторое время и другие. Так как концепция [pyOpenRPA]([https://gitlab.com/UnicodeLabs/OpenRPA]) заключается в абсолютно безвоздмездном использовании и открытости для всех, в этой RPA платформе нет каких-либо бюджетов. В связи с этим нет возможности и отстаивать монопольное право на использование названия (да это и не нужно). В связи с этим, было принято решение немного скорректировать название, чтобы избавить пользователей от возможной путаницы.
По поводу [OpenRPA от другой команды](https://github.com/open-rpa/openrpa): очень надеюсь, что им удастся реализовать свою идею, превзойти по всем параметрам платные RPA платформы, и сохранить свою открытось. В мире open source мы не конкуренты, а коллеги, которые трудятся в одном и том же направлении - в направлении создания полезного открытого продукта. Если говорить про их RPA платформу, то там поставлена цель создания аналога коммерческой RPA платформы с визуальным программированием в основе. Идея очень интересная и привлекательная, но крайне трудозатратная по исполнению (ведь не просто так лучшие RPA платформы постоянно дорабатываются огромными командами разработчиков, что ведет к комерциализации проекта). Желаю им удачи в достижении поставленных целей.
<cut/>
# Навигация по статьям
Так как [pyOpenRPA](https://gitlab.com/UnicodeLabs/OpenRPA) - это достаточно крупная RPA платформа: туториал будет составлен в виде серий статей, в которых будут освещаться ключевые технологии. А уже освоив эти технологии, у вас появится возможность углубиться в то, что вам нужно.
**Ниже приведу планируемый перечень статей по этой тематике (для навигации):**
- [Отказываемся от платных RPA платформ и базируемся на OpenSource (pyOpenRPA)](https://habr.com/ru/post/506766/)
- \>> pyOpenRPA туториал. Управление оконными GUI приложениями
- pyOpenRPA туториал. Управление WEB приложениями (то, что мы смотрим в Chrome, Firefox, Opera)
- pyOpenRPA туториал. Управление клавиатурой & мышью
- pyOpenRPA туториал. Распознавание графических объектов на экране
<cut/>
# Немного теории и терминов
Давайте попробуем разобраться в том, как устроены GUI приложения, и почему мы можем ими управлять.
Начнем с простого. Рассмотрим на примере классического блокнота, что видим мы, и что видит робот.
<cut/>
## Что видим мы?
![Notepad_human](https://habrastorage.org/webt/gk/q_/ky/gkq_kyyivmojkiynqossxayojck.png)
## Что видит робот?
![Notepad_pyOpenRPA](https://habrastorage.org/webt/nj/sj/zx/njsjzxdzjzdimm_ushvz-f5eqaw.png)
<cut/>
## Интерпретация
Благодаря архитектуре современных операционных систем у сторонних программ имеется программная возможность по обращению к [UI элементам - они же UIO](https://ru.wikipedia.org/wiki/%D0%98%D0%BD%D1%82%D0%B5%D1%80%D1%84%D0%B5%D0%B9%D1%81_%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8F) сторонних GUI приложений. Эта возможность изначально разрабатывалась для того, чтобы позволить программистам проводить [регрессионное тестирование свеого софта](https://ru.wikipedia.org/wiki/%D0%A0%D0%B5%D0%B3%D1%80%D0%B5%D1%81%D1%81%D0%B8%D0%BE%D0%BD%D0%BD%D0%BE%D0%B5_%D1%82%D0%B5%D1%81%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5), но позже выяснилось, что эту возможность можно использовать и в бизнес-процессах компании.
Как мы видим на изображении выше, для робота, блокнот - это набор различных UIO. Причем не просто UIO, а UIO c **набором различных атрибутов, и набором различных действий**. И что самое главное - наш робот имеет полный доступ ко всем этим атрибутам и действиям.
> **Примеры атрибутов<br>**
> - hidden - элемент спрятан в GUI интерфейсе от глаз пользователя<br>
> - disabled - элемент недоступен для выполнения действий (нажатие, наведение мышкой и и т.д.)<br>
> **Примеры действий**
> - left click - клик левой кнопкой мыши<br>
> - right click - клик правой кнопкой мыши<br>
> - type text - ввод текста в активную область<br>
> - scroll up - пролистывание активной области вверх<br>
> - scroll down - пролистывание активной области вниз<br>
> - scroll left - пролистывание активной области влево<br>
> - scroll right - пролистывание активной области вправо<br>
Тут вы можете мне возразить, что это все ерунда, потому что в любой операционной системе реализованы алгоритмы, обеспечивающие разграничение информационных потоков, и одно приложение не может "залезть" в другое приложение. А я вам на это скажу, что это, действительно так, **но только для программного (технического) уровня**. Вся эта история с безопасностью не работает, когда речь заходит о доступе к GUI интерфейсам других приложений.
<cut/>
## Что такое UIO?
UIO - это User Interface Object (терминология pyOpenRPA). В целях обеспечения максимальной совместимости, этот экземпляр наследуется от обьектной модели, разработанной в библиотеке [pywinauto (нажми, чтобы получить список доступных функций класса)](https://pywinauto.readthedocs.io/en/latest/code/pywinauto.base_wrapper.html).
Данный подход позволяет имплементировать полезную функциональность, которая уже была успешно разработана в других бибилотеках, и дополнить ее недостающей функциональностью. В нашем случае, недостающей функциональностью является возможность динамического обращения к UIO обьектам по UIO селекторам.
<cut/>
## Правила формирования UIO селектора (UIOSelector)
UIO селектор - это список характеристических словарей (спецификаций UIO). Данные спецификации UIO содержат условия, с помощью которых библиотека pyOpenRPA определит UIO, удовлетворяющий условиям, заданным в спецификации UIO. Индекс спецификации UIO в списке UIO селектора харакетризует уровень вложенности целевого UIO.
Говоря другим языком, UIO селектор - это перечень условий, под которые может попасть 0, 1 или n UIO.
Ниже приведен перечень атрибутов - условий, которые можно использовать в спецификациях UIO:
```
[
{
"depth_start" :: [int, start from 1] :: глубина, с которой начинается поиск (по умолчанию 1),
"depth_end" :: [int, start from 1] :: глубина, до которой ведется поиск (по умолчанию 1),
"ctrl_index" || "index" :: [int, starts from 0] :: индекс UIO в списке у родительского UIO,
"title" :: [str] :: идентичное наименование атрибута *title* искомого объекта UIO,
"title_re" :: [str] :: регулярное выражение (python диалект) для отбора UIO, у которого атрибут *title* должен удовлетворять условию данного регулярного выражения,
"rich_text" :: [str] :: идентичное наименование атрибута *rich_text* искомого объекта UIO,
"rich_text_re" :: [str] :: регулярное выражение (python диалект) для отбора UIO, у которого атрибут *rich_text* должен удовлетворять условию данного регулярного выражения,
"class_name" :: [str] :: идентичное наименование атрибута *class_name* искомого объекта UIO,
"class_name_re" :: [str] :: регулярное выражение (python диалект) для отбора UIO, у которого атрибут *class_name* должен удовлетворять условию данного регулярного выражения,
"friendly_class_name" :: [str] :: идентичное наименование атрибута *friendly_class_name* искомого объекта UIO,
"friendly_class_name_re" :: [str] :: регулярное выражение (python диалект) для отбора UIO, у которого атрибут *friendly_class_name* должен удовлетворять условию данного регулярного выражения,
"control_type" :: [str] :: идентичное наименование атрибута *control_type* искомого объекта UIO,
"control_type_re" :: [str] :: регулярное выражение (python диалект) для отбора UIO, у которого атрибут *control_type* должен удовлетворять условию данного регулярного выражения,
"is_enabled" :: [bool] :: признак, что UIO доступен для выполнения действий,
"is_visible" :: [bool] :: признак, что UIO отображается на экране,
"backend" :: [str, "win32" || "uia"] :: вид способа адресации к UIO (по умолчанию "win32"). Внимание! Данный атрибут может быть указан только для первого элемента списка UIO селектора. Для остальных элементов списка данный атрибут будет проигнорирован.
},
{ ... спецификация UIO следующего уровня иерархии }
]
```
**Пример UIO селектора**
```
[
{"class_name":"CalcFrame", "backend":"win32"}, # Спецификация UIO 1-го уровня вложенности
{"title":"Hex", "depth_start":3, "depth_end": 3} # Спецификация UIO 1+3-го уровня вложенности (так как установлены атрибуты depth_start|depth_stop, определяющие глубину поиска UIO)
]
```
*PS. Перечень функций по работе с UIO селектором представлен в модуле UIDesktop (pyOpenRPA/Robot/UIDesktop.py). Именно эти функции будут использоваться при дальнейшей разработке робота.
Ознакомиться в полным перечнем функций модуля UIDesktop [можно здесь](https://gitlab.com/UnicodeLabs/OpenRPA/-/blob/master/Wiki/05.2.-Theory-&-practice.-Desktop-app-UI-access-(win32-and-UI-automation-dlls).md)*
<cut/>
# (По шагам) робот своими руками
Вот мы и добрались до самого интересного и важного раздела этого туториала - это пошаговый пример по созданию своего первого робота с использованием pyOpenRPA.
В качестве экспериментального робота поставим себе следующую цель: **Разработать робота, который будет контролировать вид интерфейса в приложении "Калькулятор"**. Если вид интерфейса будет отличаться от вида "Программист", то робот должен будет выставить данный вид в автоматическом режиме.
<cut/>
## Шаг 0. Подготовим интерпретатор Python 3 для нового робота (развернем pyOpenRPA)
В отличии от подавляющего большинства RPA платформ, в pyOpenRPA реализован другой подход подключения к проекту. Если в остальных RPA платформах необходимо писать робота на языке этой платформы (текстовый, или графический, или скриптовый язык), то в случае pyOpenRPA именно вы определяете где, как и когда нужно использовать эту библиотеку в проекте.
Доступно несколько вариантов загрузки [pyOpenRPA](https://gitlab.com/UnicodeLabs/OpenRPA):
- Вариант 1, простой. Скачать преднастроенную портативную версию с [GitLab страницы проекта](https://gitlab.com/UnicodeLabs/OpenRPA)
- Вариант 2, посложнее. Установить pyOpenRPA в свою версию интерпретатора Python 3 (pip install pyOpenRPA)
<cut/>
## Шаг 1. Создать проект робота
Для того, чтобы начать проект робота, необходимо создать папку проекта. В дальнейшем я затрону тему организации папок проектов для промышленных программных роботов. Но на текущий момент не буду заострять внимание на этом, чтобы сконцентрироваться непосредственно на основном - на логике работы с GUI окнами.
**Создадим следующую структуру проекта:**
- Папка "RobotCalc":
- Файл "RobotCalc_1.py" - скрипт робота 1, который мы пишем сейчас
- Файл "RobotCalc_1_Run_x64.cmd" - скрипт запуска робота 1
- Файл "RobotCalc_2.py" - скрипт робота 2, дополнение
- Файл "RobotCalc_2_Run_x64.cmd" - скрипт запуска робота 2
.cmd файлы в этом проекте играют важную роль - именно благодаря этим файлам мы будем иметь возможность выполнять запуск интересующего нас робота простым кликом по файлу.
**Ниже приведу пример "RobotCalc_1_Run_x64.cmd" файла (файл "RobotCalc_2_Run_x64.cmd" аналогичен):**
```
cd %~dp0
..\Resources\WPy64-3720\python-3.7.2.amd64\python.exe "RobotCalc_1.py"
pause >nul
```
<cut/>
## Шаг 2. Запустить студию pyOpenRPA и сформировать необходимые UIO селекторы
- Открыть калькулятор (win + r > calc > enter)
**Если вы скачали преднастроенную версию pyOpenRPA с GitLab (вариант 1, простой):**
- Выполнить запуск cmd файла web студии pyOpenRPA из репозитория "pyOpenRPA\Studio\pyOpenRPA.Studio_x64.cmd"
**Если вы скачали пакет pyOpenRPA с помощью pip install pyOpenRPA (вариант 2, посложнее):**
- Выполнить запуск интерпретатора python со следующими аргументами: python -m pyOpenRPA.Studio "..\Studio\SettingsStudioExample.py", где SettingsStudioExample.py - это конфигурационный файл запуска студии pyOpenRPA. Преднастроенный шаблон этого файла можно [скачать с репозитория pyOpenRPA в GitLab](https://gitlab.com/UnicodeLabs/OpenRPA/-/blob/master/Studio/SettingsStudioExample.py)
При любом из вариантов через 5 - 15 сек. должна автоматически отобразиться web студия pyOpenRPA (см. ниже)
![pyOpenRPA_studio](https://habrastorage.org/webt/zu/pm/v7/zupmv75pwn3nseyb3mdnuzwzrwa.png)
*Внешний вид web студии pyOpenRPA*
<cut/>
- В списке открытых оконных GUI приложений найти калькулятор и активировать режим поиска UI элемента по наведению указателя мыши (Кнопка "Mouse search")
- Переключиться на калькулятор (alt + tab)
- Навести указатель мыши на тот элемент, который нам необходим для того, чтобы определить состояние интерфейса калькулятора. Выберем radio кнопку Hex. Зеленая окантовка появляется поверх калькулятора благодаря студии pyOpenRPA - именно таким образом студия сообщает нам о том, какой UI элемент она видит в калькулятора по той точке, куда наведен указатель мыши.
![calc_radiobutton](https://habrastorage.org/webt/cm/rq/qs/cmrqqsomsdbcyez1kdo6_tzmcgm.png)
*Студии pyOpenRPA подсвечивает зеленой окантовкой обнаруженный UI элемент по месту указателя мыши на калькуляторе*
- Для того, чтобы остановить процесс поиска UI элемента необходимо зажать клавишу ctrl на 2-4 секунды, после чего в WEB интерфейсе студии появится иерархния до UI элемента, который студия подсвечивала зеленой окантовкой.
<cut/>
![pyOpenRPA_studio_calc_ui_hex](https://habrastorage.org/webt/vh/ai/tl/vhaitlupj8ntvxgj10rebcgp2ay.png)
*Студия pyOpenRPA отобразила иерархию нахождения UI элемента в калькуляторе после отправки сигнала завершения поиска UI элементов (длительное нажатие ctrl)*
- Для того, чтобы убедиться в том, что элемент был обнаружен корректно, достаточно нажать кнопку "Highlight" по тому UI элементу, который интересует. Программа повторно нарисует эеленую окантовку поверх того UI элемента, который был обнаружен.
- Далее выполнить клик по UI элементу в окне иерархии в студии, после чего перейти в окно редактирования UIO селектора (UIO селектор далее будет использоваться в коде робота в Python 3)
<cut/>
![pyOpenRPA_studio_calc_ui_hex_uio](https://habrastorage.org/webt/y2/_b/ck/y2_bckz1fb7c6z9x1gpyllqv2es.png)
*Студия pyOpenRPA сформировала UIO селектор в автоматическом режиме к UI элементу калькулятора*
- В нашем примере UI элемент расположен на 4-м уровне вложенности с атрибутом title = "Hex". В автоматическом режиме pyOpenRPA формирует UIO селектор по индексам расположения в вышестоящих UI элементах. Такой подход достаточно нежелательно использовать в конечных роботах, потому что индексы расположения UI элементов могут динамически изменяться во время работы программы.
- Произведем преобразование нашего UIO селектора:
```
[{"title":"Калькулятор","class_name":"CalcFrame","backend":"win32"},{"ctrl_index":0},{"ctrl_index":6},{"ctrl_index":1}]
```
в следующий вид:
```
[{"class_name":"CalcFrame","backend":"win32"},{ "title":"Hex", "depth_start":3, "depth_end": 3}]
```
- В результате преобразований убрали лишнее условие "title":"Калькулятор" и промежуточные уровни, которые характеризовались только индексами нахождения UI элементов. Вместо этого добавили условие поиска "title":"Hex" и установили область поиска "depth_start":3, "depth_end": 3 (в нашем случае это необходимо, потому что мы убрали явные уровни вложенности). Атрибуты "class_name" накладывает условие, что надо искать прилоежние с class_name = CalcFrame, а backend указывает pyOpenRPA, какую систему поиска UI элементов использовать (win32 или uia, у каждой и них есть + и -)
- С помощью кнопки "Hightlight element" убедимся в том, что UI элемент, по-прежнему, обнаруживается студией pyOpenRPA (при нажатии на кнопку поверх UI элемента должна быть отрисована зеленая окантовка - новый UIO селектор работает корректно)
<cut/>
- Данный UIO селектор будем использовать в роботе для проверки состояния интерфейса калькулятора: если UI элемент успешно обнаруживается, то режим калькулятора установлен верный. Если UI элемент не обнаруживается, то режим калькулятора установлен неверный, и его нужно будет изменить. Для того, чтобы проверить наличие UI элемента по UIO селектору воспользуемся функцией **pyOpenRPA.Robot.UIDesktop.UIOSelector_Exist_Bool**
```
lCalcHex_IsExistBool = UIDesktop.UIOSelector_Exist_Bool(inUIOSelector=[{"class_name":"CalcFrame","backend":"win32"},{ "title":"Hex", "depth_start":3, "depth_end": 3}]) # Проверить наличие UI элемента по UIO селектору
```
- Для того, чтобы установить режим программиста, воспользуемся еще одной возможностью win32 - активация события, расположенного в меню приложения (см. ниже).
![Calc Vid Programmist](https://habrastorage.org/webt/-u/u-/ss/-uu-ss0_0dp6bg5pnw8opmbay38.png)
*Вид "Программист" в калькуляторе*
Активация элемента меню выполняется с помощью специальной функции *menu_select* у корневого UIO объекта GUI приложения.
<cut/>
- С помощью студии pyOpenRPA сформируем UIO селектор корневого объекта калькулятора
```
lUIOSelectorCalculator = [{"title":"Калькулятор","class_name":"CalcFrame","backend":"win32"}] # Сформировали UIO селектор из студии pyOpenRPA
```
- Далее запросим UIO объект по UIO селектору, после чего вызовем функцию *menu_select*, в которую передадим строковый адрес вызываемого элемента меню
```
lUIOCalculator = UIDesktop.UIOSelector_Get_UIO(inSpecificationList=lUIOSelectorCalculator) # Получить UIO экземпляр
lUIOCalculator.menu_select("&Вид -> &Программист") # Выполнить смену режима калькулятора
```
<cut/>
## Шаг 3. Консолидируем код в проекте робота
Обадая всеми необходимыми UIO селекторами и функциями, перейдем к составлению целостного скрипта робота. Ниже я приведу код RobotCalc_1.py файла, готового для запуска (python.exe "RobotCalc_1.py") c детальным описанием каждой строки.
```
from pyOpenRPA.Robot import UIDesktop # Импорт модуля, который умеет управлять UI элеметами GUI приложений
import time # Библиотека сна алгоритма
import os # Библиотека системных функций, позволит открять калькулятор, если это потребуется
lUIOSelectorCalculator = [{"title":"Калькулятор","class_name":"CalcFrame","backend":"win32"}] # Сформировали UIO селектор из студии pyOpenRPA
while True: # Вечный цикл
lUIOCalculator = UIDesktop.UIOSelector_Get_UIO(inSpecificationList=lUIOSelectorCalculator) # Получить UIO экземпляр
lCalcHex_IsExistBool = UIDesktop.UIOSelector_Exist_Bool(inUIOSelector=[{"class_name":"CalcFrame","backend":"win32"},{ "title":"Hex", "depth_start":3, "depth_end": 3}]) # Проверить наличие UI элемента по UIO селектору
if not lCalcHex_IsExistBool: # Проверить, что UI элемент отсутствует
lUIOCalculator.menu_select("&Вид -> &Программист") # Выполнить смену режима калькулятора
time.sleep(1) # Выполнить сон на 1 сек., после чего перейти на следующую итерацию
```
Внимание! При запуске робота убедитесь в том, что калькулятор находится в активном состоянии на экране вашего компьютера. Робот начнет отслеживать состояние калькулятора. Если в калькуляторе не будет установлен режим программиста, то робот в течение 1 секунды вернет его в данный режим.
<cut/>
## Дополнение. Дорабатываем робота, чтобы он еще включал калькулятор (если он выключен), раскрывал его (если он свернут)
- Для решения поставленной задачи мы уже обладаем всеми необходимыми UIO селекторами. Необходимо только определиться с функциями, которые дополнительно будем использовать.
- Для запуска калькулятора будем использовать функцию *os.system*
```
os.system("calc") # Открыть калькулятор
```
<cut/>
- Для проверки состояния окна (свернуто в трэй или развернуто) *is_minimized*
```
lUIOCalculator.is_minimized()
```
- Для восстановления свернутого окна будем использовать функцию *restore*
```
lUIOCalculator.restore() # Восстановить окно калькулятора из свернутого вида
```
<cut/>
- Итого получим следующий исходный код робота (файл RobotCalc_2.py).
```
from pyOpenRPA.Robot import UIDesktop # Импорт модуля, который умеет управлять UI элеметами GUI приложений
import time # Библиотека сна алгоритма
import os # Билбиотека системных функций, позволит открять калькулятор, если это потребуется
lUIOSelectorCalculator = [{"title":"Калькулятор","class_name":"CalcFrame","backend":"win32"}] # Сформировали UIO селектор из студии pyOpenRPA
while True: # Вечный цикл
lExistBool = UIDesktop.UIOSelector_Exist_Bool(inUIOSelector=lUIOSelectorCalculator) # Проверить наличие окна по UIO селектору
if not lExistBool: # Проверить наличие окна калькулятора
os.system("calc") # Открыть калькулятор
else: # Проверить, что окно калькулятора не свернуто
lUIOCalculator = UIDesktop.UIOSelector_Get_UIO(inSpecificationList=lUIOSelectorCalculator) # Получить UIO экземпляр
if lUIOCalculator.is_minimized(): # Проверить, что калькулятор находится в свернутом виде
lUIOCalculator.restore() # Восстановить окно калькулятора из свернутого вида
else:
lCalcHex_IsExistBool = UIDesktop.UIOSelector_Exist_Bool(inUIOSelector=[{"class_name":"CalcFrame","backend":"win32"},{ "title":"Hex", "depth_start":3, "depth_end": 3}]) # Проверить наличие UI элемента по UIO селектору
if not lCalcHex_IsExistBool: # Проверить, что UI элемент отсутствует
lUIOCalculator.menu_select("&Вид -> &Программист") # Выполнить смену режима калькулятора
time.sleep(1) # Выполнить сон на 1 сек., после чего перейти на следующую итерацию
```
*PS 1. Для сравнения: Реализация аналогичного алгоритма в другой RPA платформе с помощью инструментов визуального программирования потребует в **3-4 раза больше пространства рабочей области экрана** (в связи со спецификой визуального программирования).*
*PS 2. Перечень всех функций в модуле UIDesktop (pyOpenRPA/Robot/UIDesktop.py)
Ознакомиться в полным перечнем функций модуля UIDesktop [можно здесь](https://gitlab.com/UnicodeLabs/OpenRPA/-/blob/master/Wiki/05.2.-Theory-&-practice.-Desktop-app-UI-access-(win32-and-UI-automation-dlls).md)*
<cut/>
# Подведем итоги
Итак, мы успешно преодолели первые шаги по созданию бесплатных программных роботов. Безусловно, эта статья покрывает далеко не все области программной роботизации. В следующих статьях-туториалах мы остановимся на оставшихся "столпах" роботизированного управления (мышь, клавиатура, распознавание изображения с экрана и web манипуляции).
Надеюсь, что рассмотренные технологии, в первую очередь, помогут вам или вашей компании в достижении поставленных целей. А во вторую очередь, пусть получают выгоду и другие участники RPA рынка (да, я про вендоров платных RPA платформ, многие из которых базируются в США).
Всегда открыт к вашим комментариям, и буду рад помочь вам в решении ваших вопросов.
До скорых публикаций!

@ -0,0 +1,11 @@
# Навигация по статьям
Так как [pyOpenRPA](https://gitlab.com/UnicodeLabs/OpenRPA) - это достаточно крупная RPA платформа: туториал будет составлен в виде серий статей, в которых будут освещаться ключевые технологии. А уже освоив эти технологии, у вас появится возможность углубиться в то, что вам нужно.
**Ниже приведу планируемый перечень статей по этой тематике (для навигации):**
- [pyOpenRPA туториал. Управление оконными GUI приложениями](DesktopGUI_Habr/README.md)
- pyOpenRPA туториал. Управление WEB приложениями (то, что мы смотрим в Chrome, Firefox, Opera)
- pyOpenRPA туториал. Управление клавиатурой & мышью
- pyOpenRPA туториал. Распознавание графических объектов на экране

@ -0,0 +1,17 @@
# The OpenRPA Wiki
Dear friend! <br>
Welcome to the OpenRPA wiki portal. Here you can find theory & practical docs to work with first Open Source RPA platform.
## Content
In wiki you can find:
- [About OpenRPA, library dependencies and licensing](01.-About-OpenRPA,-library-dependencies-and-licensing.md)
- [Architecture (Studio, Robot, Orchestrator)](02.-Architecture-(Studio,-Robot,-Orchestrator).md)
- [How to install (system requirements)](03.-How-to-install-(system-requirements).md)
- [Tool Studio: How to use](04.1.-Tool-Studio.-How-to-use.md)
- [Tool Robot: How to use](04.2.-Tool-Robot.-How-to-use.md)
- Tool Orchestrator: How to use
- [Theory & practice: Web app access (Chrome, Firefox, Opera)](05.1.-Theory-&-practice.-Web-app-access-(Chrome,-Firefox,-Opera).md)
- [Theory & practice: Desktop app UI access (win32 and UI automation dlls)](05.2.-Theory-&-practice.-Desktop-app-UI-access-(win32-and-UI-automation-dlls).md)
- Theory & practice: Keyboard & mouse manipulation
- Theory & practice: Screen capture & image recognition

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Loading…
Cancel
Save