Lab 1.4: Desenvolvendo automação Desktop com IDs
Além da possibilidade de utilizar visão computacional, outra alternativa quando estamos em um ambiente Windows é acessar os elementos de uma aplicação através dos IDs dos componentes.
Para isso, utilizamos uma ferramenta onde conseguimos inspecionar a árvore de elementos, e com base nos atributos desses elementos podemos acessá-los e manipulá-los no código da automação.
BotCity Windows Inspector
Para esse treinamento, vamos usar a ferramenta BotCity Windows Inspector, que foi instalada no laboratório anterior.
Com essa ferramenta conseguimos inspecionar elementos de aplicações Windows e gerar a base do código Python para acessá-los, conforme as configurações desejadas.
Clique em Launch Windows API Inspector. A janela do Inspector estará disponível.
Uso básico
A ferramenta tem algumas funcionalidades e configurações, os principais são:
-
Botão Start:
Pressionando o botão start, a ferramenta entra em modo de inspeção, onde o cursor do mouse pode ser utilizado para selecionar os elementos da aplicação, veja que uma borda vermelha é exibida ao redor do elemento para identificação.
Nesse momento pressione o atalho
Ctrl + Alt + C
para que o elemento seja inspecionado, gerando a árvore de elementos.Por fim pressione
ESC
para finalizar a seleção. -
Botão Reset:
Pressionando o botão reset, a ferramenta volta ao estado inicial, limpa a árvore de elementos.
-
Janela Inspection Tree:
Exibe a árvore de elementos inspecionados, desde a raiz até os elementos selecionados. É possível selecionar qualquer nó dessa árvore para visualizar as informações do elemento.
-
Janele Mapped Elements:
Exibe os elementos mapeados, ou seja, os elementos que foram selecionados e que serão utilizados para gerar o código. Também é possível selecionar os diferentes elementos selecionados para visualizar as informações.
-
Janela Property/Value:
Exibe as propriedades e valores do elemento selecionado, como
title
,class_name
,control_type
, etc. Esses valores podem servir para refatorar o código gerado, deixando-o mais resiliente. -
Janela Code:
Nessa janela podemos selecionar algumas propriedades para gerar o código, como: tipo de backend, se o código deve ser gerado para abrir a aplicação, se o código deve ser gerado para conectar com a aplicação, etc.
-
Botão Generate Code:
Pressionando o botão Generate Code, o código será gerado com base nos elementos e configurações selecionadas.
Alternativas
Uma alternativa para quem não utiliza o VSCode é a ferramenta Accessibility Insights for Windows. Essa ferramenta permite a inspeção de elementos de aplicações Windows, fornecendo recursos básicos.
Exemplo utilizando o bloco de notas:
Com a ferramenta de inspeção aberta, posicione o cursor do mouse em cima da aplicação que queremos inspecionar para visualizar as informações e utilizá-las no código.
Criando um projeto usando o template
Vamos criar um novo projeto utilizando o template da BotCity com os seguintes comandos:
- Instala o cookiecutter
python -m pip install --upgrade cookiecutter
- Download do template
python -m cookiecutter https://github.com/botcity-dev/bot-python-template/archive/v2.zip
O sistema solicitará algumas respostas para criar seu projeto:
- project_type:
1
| Tipo de projeto Desktop. - bot_id:
BotSicalcID
| Nome do projeto.
Após o término do processo acima, uma nova pasta chamada BotSicalcID
estará disponível.
Criando ambiente isolado
Como boa prática, criaremos um ambiente virtual isolado para essa automação, evitando problema como diferentes versões de dependências. Para isso abra o terminal na pasta do projeto e use os seguintes comandos:
- Cria o ambiente:
python -m venv venv
- Ativa o ambiente:
venv\Scripts\activate
Instalando as dependências do projeto
Utilizando a linha de comando, acesse a pasta BotSicalcID
que criamos nas etapas anteriores e a partir desta pasta rode o comando abaixo para instalar as dependências definidas no arquivo requirements.txt
:
pip install --upgrade -r requirements.txt
Gerando arquivos de DARF através do aplicativo SiCalc
Ainda considerando o processo que desenvolvemos anteriormente, vamos fazer uma versão do mesmo processo utilizando essa nova abordagem de IDs ao invés da visão computacional.
Executando o aplicativo SiCalc
Para iniciar o processo, vamos criar uma automação desktop a partir do template e limpar o arquivo bot.py
, deixando apenas a função main
onde os códigos serão gerados.
def main():
...
if __name__ == "__main__":
main()
Com o aplicativo SiCalc aberto, vamos fazer a conexão inicial, para isso clique em Start na ferramenta BotCity Windows Inspector, posicione o mouse sobre a janela principal do aplicativo, pressione Ctrl + Alt + C
para inspecionar o elemento e em seguida ESC
para finalizar a seleção.
No inspector, selecione a janela e o elemento e marque as seguintes opções na janela code:
- Backend Win32
- Generate code to launch the app
- Generate code to connect to the app
Vá até a função main()
do código e coloque o mouse onde o código deve ser inserido, então clique em Generate Code.
Esse código será responsável por abrir o aplicativo SiCalc, conectar com a instância aberta e deixá-la em foco.
def main():
# Import for DesktopBot
from botcity.core import DesktopBot, Backend, Application
# Instantiating DesktopBot
bot = DesktopBot()
# Application path
app_path = r"C:\Program Files (x86)\Programas RFB\Sicalc Auto Atendimento\SicalcAA.exe"
# Launching the app
bot.execute(app_path)
# Connecting to the application using 'path' selector
# Adjust the 'Backend' type according to your application
current_app = bot.connect_to_app(backend=Backend.WIN_32, path=app_path)
# Getting the main window reference
main_window = current_app.top_window() if isinstance(current_app, Application) else current_app
main_window.set_focus()
Nota
Tenha atenção no código gerado, podemos refatorá-lo colocando os imports no início do código e limpar algumas sugestões comentadas.
Identificando pop-up da tela inicial
Seguindo o processo, vamos identificar o botão continuar do pop-up na tela inicial do aplicativo SiCalc.
Clique em Start na ferramenta BotCity Windows Inspector, com o mouse em cima do botão, pressione Ctrl + Alt + C
para inspecionar o elemento e em seguida ESC
para finalizar a seleção.
Assim que os elementos aparecerem na ferramenta pressione ESC
para finalizar a seleção e selecione a seguinte opção:
- Generate code for mapped elements + parents
O código gerado sugere algumas opções de manipulação de elementos. Vamos fazer algumas alterações para o reconhecimento correto da pop-up e do botão continuar, atenção as linhas destacadas:
parent_1 = bot.find_app_element(
waiting_time=10000,
from_parent_window=main_window,
best_match="Esclarecimento ao Contribuinte",
class_name="ThunderRT6FormDC"
)
target_element = bot.find_app_element(
waiting_time=10000,
from_parent_window=parent_1,
best_match="Continuar",
class_name="ThunderRT6CommandButton"
)
## Perform a default click action on the button
target_element.click()
## Perform a click action with a mouse move on the button
# target_element.click_input()
Refatorando o código
No primeiro trecho, o código busca pelo pop-up com o título "Esclarecimento ao Contribuinte" como um elemento, porém o pop-up é uma janela, então vamos alterar:
- variável:
parent_1 -> pop_up
- método:
find_app_element() -> find_app_window()
- parâmetro:
remover -> from_parent_window
No segundo trecho, precisamos clicar no botão "Continuar" dentro dessa pop up. Vamos alterar:
- variável:
target_element -> btn_continuar
- parametro:
from_parent_window=parent_1 -> from_parent_window=pop_up
- opcionais:
remover -> ações comentadas
O código refatorado ficará da seguinte forma:
# Conecta ao pop up
pop_up = bot.find_app_window(
waiting_time=10000,
best_match="Esclarecimento ao Contribuinte",
class_name="ThunderRT6FormDC"
)
# Clica em continuar dentro do pop up
btn_continuar = bot.find_app_element(
waiting_time=10000,
from_parent_window=pop_up,
best_match="Continuar",
class_name="ThunderRT6CommandButton"
)
btn_continuar.click()
Selecionando opção do menu
Após fechar o pop-up, vamos pegar a referência da janela principal novamente.
Para isso, clique em Start na ferramenta BotCity Windows Inspector, com o mouse em cima da janela principal, pressione Ctrl + Alt + C
para inspecionar o elemento.
Com a árvore de elementos pronta, selecione a janela principal, marque as seguintes opções na janela code e clique em Generate Code:
- Backend Win32
- Generate code to connect to the app
Com o código de conexão com a janela principal reestabelecido e vamos acessar o menu através do método menu_select
para abrir o formulário, como na linha destacada:
# Conexão reestabelecida
current_app = bot.connect_to_app(backend=Backend.WIN_32, path=app_path)
main_window = current_app.top_window() if isinstance(current_app, Application) else current_app
main_window.set_focus()
# Acesso ao menu
main_window.menu_select("Funções -> Preenchimento de DARF")
Informações adicionais
Para explorar mais sobre seletores e métodos disponíveis, veja na documentação pywinauto.
Preenchendo formulário inicial
Neste primeiro formulário, vamos preencher somente o código da receita.
Para isso, clique em Start na ferramenta BotCity Windows Inspector, com o mouse em cima da janela, pressione Ctrl + Alt + C
para inspecionar o elemento.
Selecione a opção e clique em Generate Code:
- Backend Win32
- Generate code only for mapped elements
Assim será gerado o código de foco no formulário, atenção as linhas destacadas:
target_element = bot.find_app_element(
waiting_time=10000,
from_parent_window=None, # Adjust to reference the correct parent element if necessary
best_match="Preenchimento de DARF",
class_name="ThunderRT6FormDC"
)
## Set the focus to this element
target_element.set_focus()
## Maximize the window
# target_element.maximize()
## Close the window
# target_element.close()
Com o elemento do formulário encontrado, vamos buscar pela referência do campo "Cód.Receita", o qual precisamos preencher, mas notamos que esse elemento em específico não possui atributos únicos que possam identificá-lo.
Quando não é possível acessar um elemento pelos seus atributos únicos, podemos acessá-lo através do seu tipo:
Identificamos que esse elemento é do tipo Edit
, para selecionar o campo "Cód.Receita" adicionamos seu index referente na árvore, nesse caso 3
.
Como esse elemento é do tipo Edit
, conseguimos inserir um conteúdo de texto através do método type_keys
.
Refatorando o código
Vamos fazer algumas alterações no código gerado:
- variável:
target_element -> formulario1
- método:
from_parent_window=None -> from_parent_window=main_window
- opcionais:
remove -> ações comentadas
- campo:
adiciona -> formulario.Edit3.type_keys("5629")
- navegação:
adiciona -> formulario.type_keys("{TAB}")
O código refatorado ficará da seguinte forma:
# Referência do primeiro formulário
formulario1 = bot.find_app_element(
waiting_time=10000,
from_parent_window=main_window,
best_match="Preenchimento de DARF",
class_name="ThunderRT6FormDC"
)
formulario1.set_focus()
# Acessando campo 'Edit' referente ao "Cód.Receita"
# a partir da janela do formulário encontrada anteriormente
formulario1.Edit3.type_keys("5629")
# Tecla tab para avançar o processo (equivalente ao bot.tab())
formulario1.type_keys("{TAB}")
Preenchendo os dados da DARF
Ao executarmos esse código, será aberto um segundo formulário onde vamos inserir os demais dados da DARF.
Agora, a tela será parecida com essa:
Assim como no passo anterior, vamos utilizar a ferramenta de inspeção para encontrar a referência do formulário e preencher campos que serão preenchidos.
Mantenha selecionada a opção e clique em Generate Code:
- Backend Win32
- Generate code only for mapped elements
Assim será gerado o código encontrando o novo formulário, atenção a linha destacada:
target_element = bot.find_app_element(
waiting_time=10000,
from_parent_window=None, # Adjust to reference the correct parent element if necessary
best_match="Receita",
class_name="ThunderRT6Frame"
)
Seguindo a mesma estratégia anterior, como os campos não tem atributos únicos, vamos acessá-los através do tipo Edit
e sua posição na árvore.
Refatorando o código
Vamos fazer algumas alterações no código gerado:
- variável:
target_element -> formulario2
- método:
from_parent_window=None -> from_parent_window=main_window
- campos:
adiciona -> formulario2.EditX.type_keys("...")
- navegação:
adiciona -> formulario2.type_keys("{...}")
- ação opcional:
adiciona -> bot.wait(2000)
O código refatorado ficará da seguinte forma:
# Referência do segundo formulário
formulario2 = bot.find_app_element(
waiting_time=10000,
from_parent_window=main_window,
best_match="Receita",
class_name="ThunderRT6Frame"
)
bot.wait(2000)
formulario2.type_keys("{TAB}")
# Periodo apuração
formulario2.Edit4.type_keys("310120")
bot.wait(2000)
formulario2.type_keys("{TAB}")
# Valor em reais
bot.wait(2000)
formulario2.Edit5.type_keys("10000")
# Calcular
formulario2.type_keys("{ENTER}")
Preenchendo formulário final
Para acessarmos o formulário final, vamos clicar no botão "DARF" após o preenchimento dos dados feito anteriormente.
Outro modo interessante de acessar um elemento é verificando os atalhos de teclado aceitos pela aplicação, no caso do botão "DARF", podemos acessá-lo através do atalho ALT+F
:
Então no código, conseguimos utilizar dessa maneira, com referência do formulário 2:
# Atalho para o botão DARF
# Nessa situação, a construção "%{<tecla>}" corresponde aos atalhos que utilizam ALT
formulario2.type_keys("%{f}")
Com o último formulário aberto, vamos pegar a sua referência e preencher os campos restantes.
Assim será gerado o código para acessar o formulário final, atenção a linha destacada:
target_element = bot.find_app_element(
waiting_time=10000,
from_parent_window=None, # Adjust to reference the correct parent element if necessary
best_match="Preenchimento DARF Auto Atendimento",
class_name="ThunderRT6FormDC"
)
## Set the focus to this element
target_element.set_focus()
## Maximize the window
# target_element.maximize()
## Close the window
# target_element.close()
Esse formulário é uma nova janela, então vamos refatorar o código e seguir com a mesma estratégia anterior para buscar pelos campos através do tipo Edit
e sua posição na árvore e preencher com as informações.
Refatorando o código
Vamos fazer algumas alterações no código gerado:
- variável:
target_element -> formulario3
- método:
find_app_element() -> find_app_window()
- parâmetro:
remover -> from_parent_window
- campos:
adiciona -> formulario.EditX.type_keys("...")
- opcionais:
remove -> ações comentadas
# Referência do terceiro formulário
formulario3 = bot.find_app_window(
waiting_time=10000,
best_match="Preenchimento DARF Auto Atendimento",
class_name="ThunderRT6FormDC"
)
formulario3.set_focus()
# Nome
formulario3.Edit5.type_keys("Petrobras")
# Telefone
formulario3.Edit6.type_keys("1199991234")
# CNPJ
formulario3.Edit11.type_keys("33000167000101")
# Referencia
formulario3.Edit10.type_keys("0")
Salvando o arquivo
O último passo é salvarmos o arquivo da DARF que será gerado.
Ainda na tela do último formulário em foco, após preencher os dados vamos clicar no botão "Imprimir"
Click em Start na ferramenta BotCity Windows Inspector, com o mouse em cima do botão, pressione Ctrl + Alt + C
para inspecionar o elemento e em seguida ESC
para finalizar a seleção.
Mantenha selecionada a opção e clique em Generate Code:
- Backend Win32
- Generate code only for mapped elements
O código gerado fica como esse, atenção as linhas destacadas:
target_element = bot.find_app_element(
waiting_time=10000,
from_parent_window=None,
best_match="Imprimir",
class_name="ThunderRT6CommandButton"
)
## Perform a default click action on the button
target_element.click()
## Perform a click action with a mouse move on the button
# target_element.click_input()
Após o click, a janela do gerenciador de arquivos do Windows abrirá. Vamos encontrar a referência dessa janela com o Inspector e inserir o caminho onde o arquivo será salvo:
Mantenha selecionada a opção e clique em Generate Code:
- Backend Win32
- Generate code only for mapped elements
O código gerado fica como esse, atenção as linhas destacadas:
# Salvando arquivo PDF
target_element = bot.find_app_element(
waiting_time=10000,
best_match="Salvar Saída de Impressão como",
class_name="#32770"
)
## Set the focus to this element
target_element.set_focus()
Refatorando o código
No primeiro trecho, o código busca pelo elemento com o título "Salvar Saída de Impressão como", vamos refatorar:
- variável:
target_element -> btn_imprimir
- parâmetro:
from_parent_window=None -> from_parent_window=formulario3
No próximo trecho, vamos selecionar a referência da janela de salvar e inserir o caminho onde o arquivo será salvo:
- variável:
target_element -> save
- parâmetro:
remover -> from_parent_window=None
- ações:
adiciona -> save.type_keys("...")
- ações:
adiciona -> formulario3.type_keys("%{F4}")
O código refatorado ficará da seguinte forma:
# Referência do botão imprimir
btn_imprimir = bot.find_app_element(
waiting_time=10000,
from_parent_window=formulario3,
best_match="Imprimir",
class_name="ThunderRT6CommandButton"
)
btn_imprimir.click()
# Referencia da janela salvar
save = bot.find_app_element(
waiting_time=10000,
best_match="Salvar Saída de Impressão como",
class_name="#32770"
)
save.set_focus()
save.type_keys(r"C:\...\DARF.pdf")
save.type_keys("{ENTER}")
# Fechando janela do formulário
formulario3.type_keys("%{F4}")
Liberando Recursos: Finalizando o Sicalc
Após salvar o DARF gerado pelo robô, é importante garantir que todos os recursos utilizados durante a execução sejam corretamente liberados. Isso ajuda a manter o ambiente limpo e pronto para as próximas execuções. Para isso, utilizamos os métodos find_process
e terminate_process
do Framework Desktop da BotCity para finalizar a aplicação Sicalc de forma adequada.
# Finalizando o processo Sicalc
sicalc_process = bot.find_process("SicalcAA.exe")
if sicalc_process:
bot.terminate_process(sicalc_process)
Essa prática assegura que o processo seja encerrado corretamente, evitando conflito em execuções futuras.
Código completo
Após salvar o arquivo nosso processo está finalizado! O código ficará dessa maneira:
# Imports DesktopBot
from botcity.core import DesktopBot, Backend, Application
def main():
# Instancia DesktopBot
bot = DesktopBot()
# Caminho do app
app_path = (
r"C:\Program Files (x86)\Programas RFB\Sicalc Auto Atendimento\SicalcAA.exe"
)
# Inicia o app
bot.execute(app_path)
# Conecta com o app
current_app = bot.connect_to_app(backend=Backend.WIN_32, path=app_path)
# Pega a referência do app
main_window = (
current_app.top_window()
if isinstance(current_app, Application)
else current_app
)
main_window.set_focus()
# Conecta ao pop up
pop_up = bot.find_app_window(
waiting_time=10000,
best_match="Esclarecimento ao Contribuinte",
class_name="ThunderRT6FormDC",
)
# Clica em continuar dentro do pop up
btn_continuar = bot.find_app_element(
waiting_time=10000,
from_parent_window=pop_up,
best_match="Continuar",
class_name="ThunderRT6CommandButton",
)
btn_continuar.click()
# Reconecta com a janela principal do app
current_app = bot.connect_to_app(backend=Backend.WIN_32, path=app_path)
# Pega a referência do app
main_window = (
current_app.top_window()
if isinstance(current_app, Application)
else current_app
)
main_window.set_focus()
# Navega no menu
main_window.menu_select("Funções -> Preenchimento de DARF")
# Referência do primeiro formulário
formulario1 = bot.find_app_element(
waiting_time=10000,
from_parent_window=main_window,
best_match="Preenchimento de DARF",
class_name="ThunderRT6FormDC",
)
formulario1.set_focus()
# Acessando campo 'Edit' referente ao "Cód.Receita"
# a partir da janela do formulário encontrada anteriormente
formulario1.Edit3.type_keys("5629")
# Tecla tab para avançar o processo (equivalente ao bot.tab())
formulario1.type_keys("{TAB}")
# Referência do segundo formulário
formulario2 = bot.find_app_element(
waiting_time=10000,
from_parent_window=main_window,
best_match="Receita",
class_name="ThunderRT6Frame",
)
bot.wait(2000)
formulario2.type_keys("{TAB}")
# Periodo apuração
formulario2.Edit4.type_keys("310120")
bot.wait(2000)
formulario2.type_keys("{TAB}")
# Valor em reais
bot.wait(2000)
formulario2.Edit5.type_keys("10000")
# Calcular
formulario2.type_keys("{ENTER}")
# Fecha o formulário
formulario2.type_keys("%{f}")
# Referência do terceiro formulário
formulario3 = bot.find_app_window(
waiting_time=10000,
best_match="Preenchimento DARF Auto Atendimento",
class_name="ThunderRT6FormDC",
)
formulario3.set_focus()
# Nome
formulario3.Edit5.type_keys("Petrobras")
# Telefone
formulario3.Edit6.type_keys("1199991234")
# CNPJ
formulario3.Edit11.type_keys("33000167000101")
# Referencia
formulario3.Edit10.type_keys("0")
# Referência do botão imprimir
btn_imprimir = bot.find_app_element(
waiting_time=10000,
from_parent_window=formulario3,
best_match="Imprimir",
class_name="ThunderRT6CommandButton",
)
btn_imprimir.click()
# Referencia da janela salvar
save = bot.find_app_element(
waiting_time=10000,
best_match="Salvar Saída de Impressão como",
class_name="#32770",
)
save.set_focus()
save.type_keys(r"C:\...\DARF.pdf")
save.type_keys("{ENTER}")
# Fechando janela do formulário
formulario3.type_keys("%{F4}")
# Finalizando o processo Sicalc
sicalc_process = bot.find_process("SicalcAA.exe")
if sicalc_process:
bot.terminate_process(sicalc_process)
if __name__ == "__main__":
main()
Desafio
Desenvolvemos uma automação para o aplicativo SiCalc, onde conseguimos preencher os dados necessários para gerar uma nova DARF e salvar o arquivo em um diretório.
Como desafio, faça melhorias no código, como:
- Inserir tratamento de exceções.
- Ler dados de DARF de um arquivo de entrada.
- Enviar o arquivo gerado por e-mail.
Explore as opções de plugins disponíveis no BotCity Studio para realizar essas tarefas.