Ir para o conteúdo

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.

Feature Win Inspector

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.

Start Win Inspector

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.

Wordpad Inspection

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.

Win Inspector - Mapped Elements

Á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.

Win Inspector - Inspection Tree

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.

Win Inspector - Properties Table

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.

Win Inspector - Continue Inspection

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.

Win Inspector - Code

  • 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.

Win Inspector - Generated Code

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.

Win Inspector - Final Inspection

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

Win Inspector - Final Code

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.

Win Inspector - Selectors

Com esses valores em mãos, basta passar como um parâmetro para o método find_app_element.

target_element = bot.find_app_element(
    waiting_time=10000,
    from_parent_window=None,
    auto_id="59648",
    class_name="RICHEDIT50W",
    control_type="Document"
)
## Substituindo o 'auto_id' pelo 'title' do elemento e removendo o 'class_name'
## Também seria possível utilizá-los em conjunto, caso fosse necessário
target_element = bot.find_app_element(
    waiting_time=10000,
    from_parent_window=None,
    title="Rich Text Window",
    control_type="Document"
)

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.

Win Inspector - Generic Elements

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.

Win Inspector - Control Index

# 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.

## 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()
## Write content in the edit
target_element.type_keys("", with_spaces=True) 
## Get the text of the edit
target_element.text_block()
## 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()
## Set the focus to this element.
target_element.set_focus()
## Maximize the window
target_element.maximize()
## Close the window
target_element.close()
## Select a menu item using the path to the item
target_element.item_by_path("{Item name} -> {Item name} -> ...", exact=True).select()
## Return all menu items
target_element.items()

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:

# Executa o atalho Ctrl + A no contexto do elemento
target_element.type_keys("^a")
# Executa o atalho Ctrl + C no contexto do elemento
target_element.type_keys("^c")

No contexto do pywinauto, a tecla ALT pode ser simbolizada por: %.

Exemplos:

# Executa o atalho Alt + F4 no contexto do elemento
target_element.type_keys('%{F4}')
# Executa o atalho Alt + F + S no contexto do elemento
target_element.type_keys("%{f}{s}")
# Pressiona a tecla ENTER no contexto do elemento
target_element.type_keys('{ENTER}')
# Pressiona a tecla TAB no contexto do elemento
target_element.type_keys('{TAB}')
# Pressiona a tecla SHIFT no contexto do elemento
target_element.type_keys('{VK_SHIFT}')

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.