Headless Testing com GhostDriver

Em automação de testes, cada vez mais estão sendo usados Headless Browsers, que são, basicamente, browsers como qualquer outro, mas sem uma interface gráfica. Isso os torna bem mais rápidos do que browsers "comuns", além de serem ótimos para se usar com Integração Contínua. Para mais detalhes, o Elias Nogueira escreveu um post em seu blog explicando muito bem o que são Headless Browsers e no que costumam ser usados.

O que é Headless Testing?

Simples: Headless Testing é testar usando um Headless Browser! Hoje em dia, o Headless Browser mais utilizado é o PhantomJS, que usa a engine gráfica WebKit, a mesma usada pelo Safari e pelo Google Chrome (até a versão 27 ~ Abril/2013). Atualmente, o Chrome usa sua própria engine (chamada Blink), que é um fork do WebKit.

Diversos frameworks de testes automatizados já possuem um runner específico para rodar testes com o PhantomJS. Na documentação do PhantomJS é possível ver uma listagem dos frameworks/runners disponíveis. Podemos citar alguns mais conhecidos, como:

Por que usar?

Motivos relevantes para considerar o uso de Headless Browsers nos seus testes seriam:


GhostDriver

O GhostDriver, segundo a própria página do projeto no GitHub, é uma implementação do WebDriver Wire Protocol para o PhantomJS. Da mesma forma que temos o ChromeDriver para rodar testes com o Chrome ou o FirefoxDriver para rodar testes com o Firefox, temos o GhostDriver para rodar testes com o PhantomJS.

Instalação

Para "instalar", basta:

Lembretes:

Como usar?

Para criar uma nova instância do GhostDriver:

WebDriver driver = new PhantomJSDriver();  

Porém, geralmente temos que configurar algumas capabilities para nosso driver. As capabilities gerais do Selenium WebDriver são bem explicadas em sua wiki, enquanto algumas específicas do GhostDriver são explicadas na página do projeto. Um exemplo de como setar essas capabilities seria:

DesiredCapabilities dcaps = DesiredCapabilities.phantomjs();  
dcaps.setCapability("takesScreenshot", false);

File executavelPhantomJS = new File("src/test/resources/phantomjs.exe");  
dcaps.setCapability("phantomjs.binary.path", executavelPhantomJS.getAbsolutePath());

WebDriver driver = new PhantomJSDriver(dcaps);  

O código acima cria uma nova instância de DesiredCapabilities, seta a capability "takesScreenshot" do WebDriver com o valor "false", seta a capability "phantomjs.binary.path" do GhostDriver para usar um executável do PhantomJS dentro da pasta "src/test/resources" do meu projeto, e, finalmente, cria uma instância de PhantomJSDriver passando essas capabilities.

Passando CLI arguments do PhantomJS

O GhostDriver permite que você passe parâmetros de linha de comando específicos do PhantomJS, através da capability "phantomjs.cli.args". Esses parâmetros são listados na documentação do PhantomJS.

Uma vez, passei pelo seguinte problema: estava querendo rodar testes com o GhostDriver em um sistema, cuja versão estava no ambiente de Desenvolvimento. Todos os testes falhavam, e eu não fazia ideia do motivo. Quando tentei rodar os testes com o FirefoxDriver, apareceu uma daquelas telas de erro de certificado, como essa:

Erro certificado

Após pesquisar sobre como ignorar erros de certificado, vi que existia um parâmetro no PhantomJS chamado "--ignore-ssl-errors", cujo valor default é "false". Bastou setar esse parâmetro como "true" para resolver meu problema.

Você pode setar os CLI arguments do PhantomJS no GhostDriver da seguinte forma:

ArrayList argumentosPhantomJS = new ArrayList();  
argumentosPhantomJS.add("--ignore-ssl-errors=true");  
argumentosPhantomJS.add("--ssl-protocol=any");  
argumentosPhantomJS.add("--proxy-type=none");

DesiredCapabilities dcaps = DesiredCapabilities.phantomjs();  
dcaps.setCapability("phantomjs.cli.args", argumentosPhantomJS);

WebDriver driver = new PhantomJSDriver(dcaps);  

O código acima cria uma lista com três parâmetros do PhantomJS e os valores que desejamos setar para eles. Em seguida, seta a capability "phantomjs.cli.args" informando a lista de parâmetros e, finalmente, cria uma instância de PhantomJSDriver.


Issues conhecidas

Como o GhostDriver ainda é um projeto em desenvolvimento, é claro que existem alguns problemas conhecidos e funcionalidades ainda não implementadas, como o tratamento de alerts JavaScript.

Tratamento de alerts

O tratamento de alerts JS é uma das funcionalidades que ainda não foram implementadas no GhostDriver, e já existe uma issue no GitHub sobre esse assunto. Para esse problema, existem dois workarounds possíveis:

Workaround 1: Usar JavaScriptExecutor

Com a classe JavaScriptExecutor do WebDriver, é possível rodar código JavaScript puro nos testes. Para tratar alerts ou confirms JavaScript, basta escrever:

//Trata um alert JS
((JavascriptExecutor)driver).executeScript("window.alert = function(msg){};");

//Clica no botão OK de um confirm JS
((JavascriptExecutor)driver).executeScript("window.confirm = function(msg){return true;};");

Workaround 2: Usar callbacks do PhantomJS

Na versão 1.1.0 do GhostDriver, foi incluída uma funcionalidade que permite executar código do PhantomJS. Isso aumentou absurdamente a flexibilidade do GhostDriver, e, dessa forma, podemos resolver facilmente qualquer tratamento de alerts usando os próprios callbacks do PhantomJS, como o onAlert e o onConfirm. Para usar essa funcionalidade no tratamento de alerts/confirms, basta escrever:

PhantomJSDriver phantom = (PhantomJSDriver)driver;

//Trata um alert JS
phantom.executePhantomJS("var page = this;" +  
                "page.onAlert = function(msg) {" +
                       "console.log('ALERT: ' + msg);" +
                "};");

//Clica no botão OK de um confirm JS (para clicar no
//botão Cancel, basta colocar 'return false'
phantom.executePhantomJS("var page = this;" +  
               "page.onConfirm = function(msg) {" +
                       "console.log('CONFIRM: ' + msg);"
                       "return true;" +
               "};");
Random UnreachableBrowserException

Essa issue (https://github.com/detro/ghostdriver/issues/140) foi aberta há um ano, e se trata de uma exceção UnreachableBrowserException sendo lançada de forma intermitente. Outros usuários também tiveram o mesmo problema, alguns disseram que o problema foi resolvido após usar uma nova release do GhostDriver, outros deram algum workaround, mas até hoje não se sabe qual a raiz dessa issue. Nos projetos que usei o GhostDriver, não tive esse problema. Caso essa exceção comece a aparecer pra você, recomendo ler os comentários da issue.

SendKeys dentro de nested frames

Essa issue (https://github.com/detro/ghostdriver/issues/335) foi aberta há dois meses, mas ainda não foi investigada. Trata-se de um problema ao usar o método sendKeys (que informa um valor para um campo de texto) quando o input está dentro de nested frames (frame dentro de outro frame). Em um sistema que tentei usar o GhostDriver recentemente, infelizmente passei por esse problema. :(


Finalizando o post, recomendo a palestra do Ivan De Marino, criador do GhostDriver e um dos maiores contributors do PhantomJS, apresentada na Selenium Conference 2013 (https://www.youtube.com/watch?v=h7AedWwkbx4).
Recomendo também, como sempre, ler os links abaixo na parte de Referências, assim como atentar aos links que foram colocados ao longo do texto. Até o próximo post! :)


Referências

http://assertselenium.com/2013/03/25/getting-started-with-ghostdriver-phantomjs/
http://eliasnogueira.com/o-que-e-headless-browser-e-porque-voce-testador-deve-saber-sobre/
https://github.com/detro/ghostdriver
https://cdn.rawgit.com/detro/ghostdriver/master/binding/java/docs/javadoc/index.html
https://code.google.com/p/selenium/source/browse/java/CHANGELOG
http://phantomjs.org/
https://github.com/ariya/phantomjs/wiki/Headless-Testing


Sobre o autor: Stefan Teixeira trabalha como QA Engineer e, desde o final de 2014, tem se aventurado no mundo DevOps. É Bacharel em Ciência da Computação pela UFRJ e MBA em Garantia de Qualidade de Software pela Escola Politécnica da UFRJ. Entusiasta de Testes Automatizados (e de tudo que possa ser automatizado!), Agile Testing e da cultura DevOps.

Contatos: stefanfk@gmail.com | Twitter | LinkedIn


comments powered by Disqus