Automatizando Aplicativos Windows com o BotCity Inspector¶
Em alguns casos, podemos automatizar aplicativos do Windows por meio de propriedades de elementos, como uma alternativa à visão computacional.
Esta pode ser uma alternativa útil quando não queremos nos preocupar com problemas de resolução. Podemos simplesmente interagir com a estrutura do aplicativo por meio dos atributos de elementos e janelas.
Este tutorial o guiará pelo processo de criação de uma automação simples que interage com os elementos de um aplicativo do Windows.
Criando um novo projeto¶
O primeiro passo é criar um projeto para automação de desktop, você pode seguir os mesmos passos definidos nas seções anteriores.
Veja como criar um Projeto Bot Desktop utilizando o template de projeto.
Ferramentas de espionagem¶
Os atributos e propriedades de um aplicativo Windows podem ser visualizados usando ferramentas de espionagem.
Atualmente existem diversas ferramentas que possibilitam a inspeção de aplicativos Desktop, como é o caso do Accessibility Insights for Windows.
Porém, essas ferramentas geralmente oferecem recursos básicos na inspeção de aplicativos e não necessariamente possuem o foco no desenvolvimento de automações, fazendo com que haja um trabalho adicional na identificação das propriedades que podem ser utilizadas no código na hora buscar pelos elementos.
Pensando nesse cenário, a BotCity disponibiliza uma ferramenta focada na inspeção de aplicativos Desktop e geração de código Python integrado com o framework de desenvolvimento, deixando o uso desses recursos mais simplificado e intuitivo na hora de construir as automações.
BotCity Windows Inspector¶
Para utilizar o BotCity Windows Inspector, basta instalar o plugin do BotCity Studio para Visual Studio Code.
Após instalar a extensão e carregar um projeto Python, você já irá ter acesso à ferramenta para começar a inspecionar aplicativos no Windows.
A seguir você verá mais detalhes sobre o uso desse recurso e também boas práticas ao inspecionar aplicativos e gerar código.
Interagindo com a aplicação¶
Para este exemplo simples, usaremos o Wordpad padrão do Windows.
Com o aplicativo aberto e o auxílio do BotCity Windows Inspector, podemos começar a mapear os elementos que desejamos interagir.
Mapeando os elementos iniciais¶
Inicialmente, vamos supor que a intenção seja interagir com os botões relacionados à configuração da fonte do texto.
Podemos por exemplo mapear os botões de Negrito, Itálico e Sublinhado do Wordpad, além do botão para centralizar o texto.
Dica
Para mapear qualquer elemento utilizando o Windows Inspector, basta posicionar o mouse na região do elemento e fazer um clique com o botão esquerdo do mouse, ou simplesmente utilizar o atalho de teclado: Ctrl+Alt+C.
Recursos do Windows Inspector¶
Elementos Mapeados:
Cada elemento que é mapeado no contexto de uma aplicação, é adicionado à lista de elementos mapeados.
A partir dessa lista, você pode clicar em um elemento para visualizar as suas propriedades, ou remover um elemento que não deseja mais interagir.
Árvore de Inspeção:
Ao clicar em um elemento da lista, a árvore de inspeção correspondente será gerada.
Dessa forma, é possível visualizarmos qual o "caminho completo" partindo da janela principal da aplicação até o elemento alvo que foi mapeado.
Tabela de Propriedades:
Além da árvore de inspeção, o BotCity Windows Inspector também exibe todas as propriedades existentes de um elemento que foi mapeado.
Os valores dessa tabela podem ser usados como seletores no código na hora de buscarmos por esses elementos.
Dica
É possível visualizar a tabela de propriedades de cada elemento existente na árvore de inspeção.
Basta clicar no elemento desejado para que a tabela de propriedades correspondente seja exibida.
Continuando o mapeamento¶
Antes de entrarmos na etapa de geração código, vamos mapear mais alguns elementos.
Voltando no Wordpad, podemos clicar novamente no botão de Start
do Windows Inspector para continuarmos o mapeamento.
Dessa vez, vamos mapear por exemplo a região do documento onde o texto é inserido e também o botão para salvar o documento.
Gerando código a partir dos elementos mapeados¶
Com os elementos iniciais mapeados, já é possível gerarmos o código Python que será utilizado como base da automação.
Nessa seção, podemos definir qual será o Backend utilizado para essa aplicação e também os trechos de código adequados para o contexto atual.
- Generate code to launch the app: Gera o trecho de código responsável por lançar o aplicativo que está sendo mapeado (assumindo que toda vez que a automação for executada, o aplicativo será iniciado do zero).
- Generate code to connect to the app: Gera o trecho de código responsável por estabelecer a conexão com a instância do aplicativo aberto. Essa opção pode ser desmarcada caso você já tenha esse trecho de código na sua automação e só esteja mapeando novos elementos.
- Generate code only for mapped elements: Gera o código utilizando os seletores para cada elemento existente na lista de elementos mapeados. Essa opção não considera a referência dos elementos pais no código gerado, portanto pode ser necessário ajustar os parâmetros de acordo com a sua necessidade.
- Generate code for mapped elements + parents: Gera o código utilizando os seletores para cada elemento existente na lista de elementos mapeados considerando os elementos pais. Essa opção é a mais indicada caso você queira que o Inspector gere um código o mais próximo da versão final.
Após a configuração do código que será gerado, basta clicar no botão Generate Code para que o BotCity Windows Inspector gere o código diretamente no arquivo .py
.
Info
O código Python gerado pelo Windows Inspector é baseado no Framework Desktop da BotCity.
Caso você esteja utilizando outras bibliotecas em conjunto, certifique-se de fazer os ajustes necessários no código da sua automação.
Salvando o documento e finalizando o processo¶
Como último passo da automação, vamos mapear a janela que é aberta ao clicarmos no botão "Salvar". Podemos continuar utilizando exatamente a mesma estratégia que usamos anteriormente.
Nesse caso, ao invés de mapearmos um elemento específico da janela de "Salvar como", podemos mapear somente a região da janela principal em si.
No código, depois iremos utilizar uma estratégia para inserir o caminho e salvar o documento usando essa referência da janela.
Dica
Você pode utilizar o botão Reset
do Windows Inspector sempre que quiser limpar os elementos mapeados e reiniciar a inspeção.
Lembre-se de gerar o código antes de resetar o Windows Inspector, para evitar que você perca a referência de elementos já mapeados.
Como a base do código já foi gerada anteriormente, podemos deixar marcada somente a opção para gerar o código referente ao novo elemento mapeado.
Ajustes finais e execução do código¶
Com o código base gerado pelo BotCity Windows Inspector, podemos refatorar e realizar os ajustes que forem necessários para o processo.
Nesse caso, iremos simplesmente incluir no código o conteúdo que será inserido no documento e também o caminho para salvar o arquivo no final.
Inserindo um conteúdo de texto na área do documento:
Podemos simplesmente ajustar o método type_keys
do elemento referente ao documento, passando por parâmetro uma string com o conteúdo.
Identifique no seu código o trecho onde esse elemento está sendo manipulado e faça a seguinte modificação:
...
target_element = bot.find_app_element(waiting_time=10000, from_parent_window=main_window, auto_id="59648", class_name="RICHEDIT50W", control_type="Document")
## Write content in the edit
target_element.type_keys("Hello! Welcome to BotCity!", with_spaces=True)
## Get the text of the edit
# target_element.text_block()
Passando o caminho para salvar o arquivo:
Para salvar o arquivo, iremos utilizar uma estratégia bastante interessante usando a janela de "Salvar como" que mapeamos anteriormente.
Com a referência da janela, podemos utilizar novamente o type_keys
tanto para inserir o caminho do arquivo quanto para executar uma ação de Enter
na janela.
Identifique no seu código o trecho onde esse elemento referente a janela de "Salvar como" está sendo manipulado e faça a seguinte modificação:
...
target_element = bot.find_app_element( waiting_time=10000, from_parent_window=main_window, best_match="Save As", class_name="#32770", control_type="Window")
## Set the focus to this element
target_element.set_focus()
target_element.type_keys(r"C:\Users\Administrator\Documents\document.rtf")
target_element.type_keys("{ENTER}")
Dica
Essa estratégia para executar a ação de uma tecla específica ou um atalho de teclado no contexto de uma janela é um recurso da biblioteca pywinauto
e pode ser bastante útil dependendo do seu caso de uso.
Veja mais detalhes sobre algumas estratégias que podem ser utilizadas e outras dicas úteis na seção Explorando o código gerado pelo Inspector
Explorando o código gerado pelo Inspector¶
Ao executarmos o código final após os ajustes, o bot irá automaticamente realizar as seguintes ações:
- Abrir o Wordpad
- Estabelecer a conexão com o aplicativo
- Realizar as interações definidas: centralizar o parágrafo, clicar nos botões de Negrito, Itálico e Sublinhado
- Digitar o texto na área do documento e salvar o arquivo no caminho definido
Como citado anteriormente, o código gerado pelo Windows Inspector utiliza o módulo Windows Applications do framework de desenvolvimento desktop da BotCity.
Info
O módulo Windows Applications
é baseado nas funcionalidades da biblioteca pywinauto
.
Veja mais detalhes sobre os recursos desta biblioteca neste link.
Por padrão, o BotCity Windows Inspector sempre irá tentar gerar um código o mais completo possível. Porém, eventuais ajustes e tratamentos podem ser necessários para garantir que o processo funcione conforme o esperado.
Procurando por elementos¶
O Windows Inspector utiliza alguns seletores chaves no código que é gerado para buscar um determinado elemento.
A tabela abaixo descreve melhor alguns seletores que podem ser utilizados:
Seletor | Descrição |
---|---|
class_name | Elementos com a classe da janela |
class_name_re | Elementos cuja classe corresponde a uma expressão regular |
parent | Elementos que são filhos deste |
process | Elementos em execução neste processo |
title | Elementos com este texto |
title_re | Elementos cujo texto corresponde a esta expressão regular |
top_level_only | Apenas elementos de nível superior (padrão = True ) |
visible_only | Somente elementos visíveis (padrão= True) |
enabled_only | Somente elementos habilitados (padrão= False) |
best_match | Elementos com um título semelhante a este |
handle | O identificador do elemento a ser retornado |
ctrl_index | O índice do elemento filho a ser retornado |
found_index | O índice do elemento filho filtrado para retorna |
predicate_func | Um gancho fornecido pelo usuário para uma validação de elemento personalizado |
active_only | Somente elementos ativos (padrão= False) |
control_id | Elementos com este ID de controle |
control_type | Elementos com este tipo de controle (string; para UIAutomation elementos) |
auto_id | Elementos com este ID de automação (para UIAutomation elementos) |
framework_id | Elementos com este ID de estrutura (para UIAutomation elementos) |
backend | Nome de back-end para usar durante a pesquisa (padrão = None significa atual back-end ativo) |
Caso você deseje substituir um parâmetro que foi gerado pelo Inspector, ou queira fazer uma combinação mais complexa, basta usar a tabela de propriedades como uma referência para obter as informações de cada elemento.
Com esses valores em mãos, basta passar como um parâmetro para o método find_app_element
.
Uma outra alternativa interessante é acessar um determinado elemento pelo seu índice, em relação ao seu elemento pai.
Esse tipo de abordagem se torna bastante útil quando não existem propriedades únicas ao filtrar por um elemento, ou quando vários elementos iguais compartilham as mesmas propriedades.
No cenário acima, temos vários elementos do tipo 'Edit' com propriedades compartilhadas e sem identificadores únicos.
Isso dificultaria na busca de um elemento específico pois vários elementos poderiam corresponder ao mesmo filtro.
# Esse código acabaria ficando genérico
# pois não estaria filtrando um elemento em específico
target_element = bot.find_app_element(
waiting_time=10000,
from_parent_window=parent_form,
class_name="ThunderRT6TextBox",
control_type="Edit"
)
Nesse cenário, poderíamos usar como referência o elemento pai, e a partir dele acessar o elemento alvo através de seu índice.
# Com a referência do elemento pai,
# podemos acessar um elemento pelo seu índice
target_element = parent_form.Edit3
target_element.type_keys("", with_spaces=True)
Dica
Caso você esteja observando algum problema na hora de encontrar um elemento, ou deseja fazer um tratamento mais elaborado, fique à vontade para customizar os seletores e a lógica do código gerado pelo Windows Inspector à qualquer momento.
Visualizando as estruturas da aplicação no código¶
Caso por algum motivo você precise visualizar mais a fundo a estrutura de alguma janela ou elemento específico da aplicação diretamente no código, você pode utilizar métodos específicos do pywinauto
.
Com a referência do elemento em mãos, basta fazer a chamada para o método dump_tree()
ou print_control_identifiers()
para que a estrutura do elemento seja exibida no terminal.
Essa alternativa é bastante interessante para caso você esteja tendo problemas ao buscar por elementos específicos e queira fazer um debug.
# Obtendo a referência do elemento,
# por exemplo a janela principal do aplicativo
main_window = current_app.top_window()
# Exibindo a estrutura do componente diretamente no terminal
main_window.dump_tree()
Exemplo da saída do método dump_tree()
Através da árvore gerada, você conseguirá visualizar de forma mais detalhada a estrutura da aplicação e os identificadores dos elementos.
Control Identifiers:
Dialog - 'Document - WordPad' (L48, T42, R821, B918)
['Document - WordPadDialog', 'Dialog', 'Document - WordPad']
child_window(title="Document - WordPad", control_type="Window")
|
| Pane - 'UIRibbonDockTop' (L56, T73, R813, B189)
| ['UIRibbonDockTop', 'Pane', 'UIRibbonDockTopPane', 'Pane0', 'Pane1']
| child_window(title="UIRibbonDockTop", control_type="Pane")
| |
| | Pane - 'Ribbon' (L56, T73, R813, B189)
| | ['RibbonPane', 'Ribbon', 'Pane2', 'RibbonPane0', 'RibbonPane1', 'Ribbon0', 'Ribbon1']
| | child_window(title="Ribbon", control_type="Pane")
| | |
| | | Pane - 'Ribbon' (L56, T73, R813, B189)
| | | ['RibbonPane2', 'Ribbon2', 'Pane3']
| | | child_window(title="Ribbon", control_type="Pane")
| | | |
| | | | Pane - '' (L56, T73, R813, B220)
| | | | ['Pane4']
| | | | |
| | | | | Pane - 'Ribbon' (L56, T42, R813, B189)
| | | | | ['RibbonPane3', 'Ribbon3', 'Pane5']
| | | | | child_window(title="Ribbon", control_type="Pane")
| | | | | |
| | | | | | Toolbar - 'Quick Access' (L94, T47, R160, B69)
| | | | | | ['Toolbar', 'Quick AccessToolbar', 'Quick Access', 'Toolbar0', 'Toolbar1']
| | | | | | child_window(title="Quick Access", control_type="ToolBar")
| | | | | | |
| | | | | | | Button - 'Save' (L94, T45, R116, B69)
| | | | | | | ['Save', 'Button', 'SaveButton', 'Button0', 'Button1']
| | | | | | | child_window(title="Save", control_type="Button")
| | | | | | |
| | | | | | | Button - 'Undo' (L116, T45, R138, B69)
| | | | | | | ['Button2', 'UndoButton', 'Undo']
| | | | | | | child_window(title="Undo", control_type="Button")
| | | | | | |
| | | | | | | Button - 'Redo' (L138, T45, R160, B69)
| | | | | | | ['Redo', 'Button3', 'RedoButton']
| | | | | | | child_window(title="Redo", control_type="Button")
...
Performando ações com os elementos¶
Para determinados tipos de elementos, como botões e campos de texto, o Windows Inspector também irá gerar junto com o código algumas sugestões de ações que podem ser realizadas com esse elemento.
## Select an item from the combo box, item can be either a 0 based index of the item to select, or it can be the string that you want to select
target_element.select()
## Return the selected item text from the combo box
target_element.selected_text()
## Return the text of all items in the combo box
target_element.texts()
Dica
Caso você não tenha muita certeza do tipo de ação que precisa realizar, ou está lidando com elementos mais complexos, consulte a documentação do pywinauto
para mais detalhes sobre os métodos disponíveis para cada tipo diferente de elemento.
Como vimos anteriormente, também é possível performar a ação de uma tecla ou atalho de teclado diretamente no contexto de um elemento.
Tendo a referência do elemento alvo, podemos utilizar o método type_keys
ou send_keys
com as seguintes combinações:
No contexto do pywinauto
, a tecla CTRL
pode ser simbolizada por: ^
.
Exemplos:
No contexto do pywinauto
, a tecla ALT
pode ser simbolizada por: %
.
Exemplos:
Dica
Acesse a referência da documentação do pywinauto
para mais detalhes em ações com o teclado.
Boas práticas no uso do BotCity Windows Inspector¶
Abaixo listamos algumas dicas e pontos importantes que podem facilitar o uso do Windows Inspector durante o desenvolvimento das suas automações.
Garanta que essa é a melhor abordagem para o seu aplicativo
O funcionamento do BotCity Windows Inspector está diretamente relacionado com o comportamento do aplicativo Desktop que está sendo automatizado.
Portanto, é importante identificar se essa estratégia realmente é a melhor para o seu caso de uso.
Se a aplicação que você está automatizando não exibe muitas informações sobre os elementos ou o comportamento não permite esse tipo de interação, tente considerar outras opções como por exemplo o uso de visão computacional.
Faça o mapeamento e teste o seu código por partes
Para garantir que as referências dos elementos não sejam perdidas ao mudar o contexto, tente mapear pequenos conjuntos de elementos e gerar o código correspondente antes de fechar o aplicativo ou avançar para uma etapa subsequente do processo.
É importante que você também valide no código se a conexão com o aplicativo está sendo feita da forma adequada (lembre-se de utilizar a tecnologia de Backend compatível com sua aplicação) e se os elementos estão sendo encontrados corretamente.
Fazendo isso, você garante que ao final do processo todas as ações estão sendo realizadas conforme o esperado.
Valide as interações que os elementos mapeados aceitam
Em alguns casos, a interação com um determinado elemento poderá ser feita de diversas maneiras.
Dependendo da aplicação, a ação de um botão poderá ser feita através de um click()
ou até mesmo performando um atalho de teclado usando type_keys()
Caso você esteja tendo dificuldades usando uma determinada ação, lembre-se de testar outras opções disponíveis e validar o que funciona melhor no seu contexto.
Caso você fique com dúvidas ou queira ver mais sobre esse tipo de conteúdo, fique à vontade para explorar os canais da comunidade BotCity.