Selenium – Brincando com as configurações do Chrome, Polymer e ShadowRoot

Uau! Mas o que é tudo isso? Sim caro leitor, quando você entra do mundo do Web Scrapping, a palavra Web traz um quinquilhão de coisas junto com ela, de benefícios a dores de cabeça.

A Web não pára e apesar desta frase ser um baita clichê, ela é assustadoramente verdadeira. Vou dar um exemplo do porquê. Tem alguns anos um treco HTML5 foi lançado. Já ouviu falar dele? Pois é, junto com ele, uma tonelada de novas tags HTML e apis Javascript também vieram. Seus scripts Selenium precisaram ser atualizados.

Vida que segue e… boom! Mais atualizações. Duas delas foram mencionadas no título e é sobre elas que falarei aqui.

Eu só queria extrair uma configuração do Chrome

Sim, tudo o que eu queria era extrair uma configuração do Google Chrome instalado na máquina do usuário. A parte fácil é que essa página de configurações é um HTML exposto sob a URL chrome://settings. Ótimo! É só dar um get nessa URL e começar a mágica.

O problema é, bem, esse é o Google Chrome, criado e mantido pelo Google, provavelmente a empresa que mais expõe e propõe inovações para especificações Web atualmente. Não surpreendentemente, a página de configurações usa um bocado de coisas novas (só precisa funcionar no Chrome mesmo), entre elas, o ShadowRoot e Polymer.

Não vou me aprofundar no que cada um desses carinhas é além de uma breve explicação.

ShadowRoot

É uma espécie de elemento HTML que cria um novo contexto a partir dele. Você pode pensar nele como um iframe + ajax com muita esperteza embutida, criado para separar contextos da hierarquia. Para mais informações: https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot

Polymer

É basicamente um padrão proposto pelo Google para criação de elementos HTML personalizados. Se você sabe algo de XML, é exatamente isso, só que, cada elemento que você criar, ganha uma nova representação visual numa página HTML. Já há vários elementos criados nesse padrão. Para mais informações: https://www.polymer-project.org/

Como não poderia ser surpresa, a página de configurações do Google está recheada dessas duas coisas.

E o Selenium nisso tudo?

Bem, primeiro deixe-me explicar porque resolvi fazer tudo isso. Primeiro, porque todo programador teimoso gosta de resolver um problema chato e difícil. Segundo, tanto o ShadowRoot como o Polymer uma hora se tornarão largamente utilizados no mercado. Resolver esse problema agora me traria um melhor entendimento sobre o assunto e me prepararia para futuro. Vamos lá.

Mas calma, mas porque isso é tão difícil com o Selenium? O XPath resolve tudo para mim! Aí é que está o problema. Não resolve. A biblioteca atual do Selenium (não só VBA, mas todas as outras, já que elas dependem do driver para implementar funcionalidades) não suporta esses tipos de elemento.

O FindElementByTag simplesmente ignora tags criadas com Polymer e o ShadowRoot precisa ser explicitamente declarado ou expandido antes de ser possível acessar qualquer elemento dentro dele. Por sorte, há como resolver esse problema, com muito suor.

Expandindo o ShadowRoot

O ShadowRoot é basicamente o primeiro filho de uma determinada tag. O Javascript lhe dá uma ajuda nisso, aliás é o único meio no momento.

Supondo que você encontro este elemento:

....
</p>
<div id="id"><p>
#shadow-root
	</p>
<div id="outroId"></div>
<p>
</div>
<p>
...

Infelizmente, um driver.FindElementById(“outroId”) retornará nulo se você não expandir o ShadowRoot antes. Como fazer isso? Em VBA:

Set divId = driver.FindElementById("id")
Set shadowRoot = driver.ExecuteScript("return arguments[0].shadowRoot", divId)
Set divOutroId = shadowRoot.FindElementById("outroId")

A primeira linha é mais do que clara.
A segunda faz a mágica de expandir o ShadowRoot que pertence à primeira div e o retorna para a variável shadowRoot.
A terceira, já de posse o elemento que representa o ShadowRoot, aplica o FindElementById para a captura do elemento procurado, desta vez retornando o de forma válida.

Capturando tags Polymer

Elementos Polymer como explicado anteriormente, são basicamente tags HTML, porém, personalizadas. Portanto, ao invés de ter algo assim:

<div id="endereco"><p>
	</p>
<div id="rua">Rua Principal</div>
<p>
	</p>
<div id="numero">1000</div>
<p>
	</p>
<div id="bairro">Capital</div>
<p>
</div>

Teriámos algo assim:

<endereco>
	<rua>Rua Principal</rua>
	<numero>1000</numero>
	<bairro>Capital</bairro>
</endereco>

É de falto mais semântico, legível e auto-explicativo. E segundo exemplo não existe na especificação HTML e um navegador não sabe reconhecê-lo, mas o Polymer torna isso possível.

O porém para nós, nobres web scrappers, a coisa não é tão simples. Veja que pela auto declaração dos elementos pela sua própria semântica, não há necessidade de ids ou outros atributos identificadores. Onde antes faríamos um:

driver.FindElementById("rua")
'ou
driver.FindElementsByTag("div")(2)
'ou
driver.FindElementByXPath("//div[@id=""rua""]")

Com Polymer faríamos:

driver.FindElementByTag("rua")

Mas não! O Selenium não sabe reconhecer esse tipo de tag! Qualquer tentativa de fazê-lo com FindElementByTag ou FindElementByXPath falhará. Como resolver isso? Assim:

AVISO: O FindElementByTag curiosamente funciona para o primeiro elemento da cadeia, ou seja, um elemento pai. Por isso, o código abaixo usa o FindElementByTag no elemento endereço sem maiores erros.

Set endereco = driver.FindElementByTag("endereco")
Set rua = driver.ExecuteScript("return arguments[0].querySelectorAll('rua')[0];", endereco)

Mais uma vez, o Javascript resolvendo o problema. A variável rua é um WebElement válido a partir de agora.

Um caso real – Obtendo o diretório local de downloads do Chrome

Pois é, essa simples necessidade gerou todo o artigo acima. Basimente, precisei navegar até a página de configurações do navegador do Google, toda feita usando ShadowRoot e Polymer e extrair a informação. Como disso anteriormente, é uma página HTML, por isso, convido-o a acessar a mesma e olhar o código fonte.

No fim, o código que executa a proeza é:

Dim driver As WebDriver
 
Sub ObtemCaminhoDownload()
    Dim shadowRoot As WebElement, _
        settingsUI As WebElement, _
        main As WebElement, _
        settingsBasicPage As WebElement, _
        advancedPage As WebElement, _
        settingsSection As WebElement, _
        settingsDownloadsPage As WebElement, _
        settingsAnimatedPage As WebElement, _
        neonAnimatable As WebElement, _
        defaultDownloadPath As WebElement
 
    Set driver = New ChromeDriver
    driver.Get "chrome://settings"
    Set settingsUI = driver.FindElementByTag("settings-ui")
    Set shadowRoot = driver.ExecuteScript("return arguments[0].shadowRoot", settingsUI)
    Set main = shadowRoot.FindElementById("main")
    Set shadowRoot = driver.ExecuteScript("return arguments[0].shadowRoot", main)
    Set settingsBasicPage = driver.ExecuteScript("return arguments[0].querySelectorAll('settings-basic-page')[0];", shadowRoot)
    Set shadowRoot = driver.ExecuteScript("return arguments[0].shadowRoot", settingsBasicPage)
    Set advancedPage = shadowRoot.FindElementById("advancedPage")
    Set settingsSection = driver.ExecuteScript("return arguments[0].querySelectorAll('settings-section')[2];", advancedPage)
    Set settingsDownloadsPage = driver.ExecuteScript("return arguments[0].querySelectorAll('settings-downloads-page')[0]", settingsSection)
    Set shadowRoot = driver.ExecuteScript("return arguments[0].shadowRoot", settingsDownloadsPage)
    Set settingsAnimatedPage = driver.ExecuteScript("return arguments[0].querySelectorAll('settings-animated-pages')[0];", shadowRoot)
    Set neonAnimatable = driver.ExecuteScript("return arguments[0].querySelectorAll('neon-animatable')[0];", settingsAnimatedPage)
    Set defaultDownloadPath = neonAnimatable.FindElementById("defaultDownloadPath")
    Debug.Print defaultDownloadPath.Text
End Sub

Basicamente, usando todas os truques citados acima. Todas as outras tentativas, usando os caminhos tradicionais não funcionarem, sendo esse o único caminho atual.

Vai ser sempre assim?

Não. Encontrei várias referências de que o time dos drivers está trabalhando para adicionar métodos nativos para tratar com ShadowRoot e fazer os métodos baseados em tags suportarem o Polymer. Não há previsão de quando isso será lançado, mas é um trabalho em andamento e a complexidade mostrada acima pode não ser mais necessária, o que será um alívio.

Recomendação – Aprenda Como Automatizar suas Tarefas no SAP com Excel VBA – Márcio Ribeiro


Sim, você entendeu. Isso é uma recomendação!

Para que acompanha o canal no YouTube (onde mais?), sabe que já trabalho em parceiria com o Márcio Ribeiro tem um bom tempo.  Dessa parceiria, surgiu a Série Versus, que dispensa apresentações.

O Márcio, além de um profissional mais do que qualificado na área de tecnologia, especialmente Excel e programaçào, também é um comunicador natural. Disso, é esperado que logo ele lançaria cursos que precederia a qualidade dos materiais que ele publica. E ele o fez!

Com você, o curso Aprenda Como Automatizar suas Tarefas no SAP com Excel VBA com Márcio Ribeiro!

Elogios são desnecessários. Tanto a página pessoal quanto seu canal do Youtube são recheados deles. O que realmente gostaria de compartilhar é que eu tive a oportunidade de avaliar o curso a convite do próprio Márcio.

O que posso dizer em um resumo é:

  • É completo
  • É focado
  • É claro e objetivo

Nem preciso dizer que esse curso reúne em um só material 2 tecnologias muito bem colocadas no mercado atualmente

  • Excel
  • VBA
  • SAP

Isso sem falar que as três trabalhando juntas culminam num efeito dos quais mais me familiarizo: Automação!

Se você é um profissional que tem em seu portifólio as qualificações mencionadas acima, dificilmente enfrentará problemas no mercado de trabalho.

Se interessou? Veja como saber mais de adquirir o curso clicando no link abaixo:

https://eduzz.com/curso/eXBR/.html?a=77684510

Ao colega Márcio Ribeiro, meus parabéns pelo trabalho e sucesso!

VBA – Carregando uma Imagem no UserForm

Está aí uma dúvida mais comum do que esperava. Carregar uma imagem num UserForm (ou planilha se preferir)? Siga os passos abaixo:

Abra o VBA (Alt+F11 com o Excel aberto)

Insira um UserForm:

Insira um UserForm
Insira um UserForm

Insira um controle de imagem no UserForm:

Insira um controle de imagem no UserForm
Insira um controle de imagem no UserForm

Arraste um botão de comando logo abaixo da Imagem:

Arraste um botão de comando logo abaixo da Imagem
Arraste um botão de comando logo abaixo da Imagem

Selecione o botão e na caixa de propriedades (clique em F4 se ela não estiver aparecendo), mude o texto do botão (Caption):

Selecione o botão e na caixa de propriedades mude o texto do botão
Selecione o botão e na caixa de propriedades mude o texto do botão

Clique duas vezes no botão para gerar o evento de Click:

Clique duas vezes no botão para gerar o evento de Click
Clique duas vezes no botão para gerar o evento de Click

E finalmente, o código:

Private Sub CommandButton1_Click()
    caminhoArquivo = Application.GetOpenFilename(FileFilter:="Image Files(*.jpg), *.jpg")
    Me.Image1.Picture = LoadPicture(caminhoArquivo)
End Sub

Execute o UserForm clicando em F5 e ao clicar no botão “Buscar Imagem”, você será apresentado a uma tela de escolha de arquivo como essa (já filtrando por arquivos de imagem. Reparei no filtro feito no código na linha 2):

Selecionando sua imagem
Selecionando sua imagem

Após selecionar a imagem, o resultado será parecido com este:

O resultado
O resultado

O mais atentos saberão que o código contém um bug. Se quiser saber a resposta, veja este link.

Arquivo: Carregar_Imagem_UserForm

Em vídeo!

Bom proveito!

VBA – Ocultando o navegador no Selenium

Eis um recurso amplamente solicitado pela comunidade que as empresas detentoras dos navegadores (com excessão do IE e Edge) acabaram por disponibilizar, que é a habilidade de ocultar o navegador durante a execução do seu script executando Selenium. E a linha de código para tal é simples:

driver.AddArgument ("--headless")

A linha acima funcionará perfeitamente no Chrome e Firefox. Abaixo o código mais completo:

Dim driver As WebDriver
    Sub Teste()
    Set driver = New ChromeDriver
 
    driver.AddArgument ("--headless")
 
    driver.Get "https://www.dolarhoje.com"
 
    Dim valor As String
    valor = driver.FindElementById("nacional").Value
 
    driver.Quit
    MsgBox valor
End Sub

E como não poderia faltar:

Tecnologia e Programação