11 May 2014 on selenium, headless testing, java
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:
WebDriver -> GhostDriver
Capybara -> Poltergeist
Jasmine -> Chutzpah
Robot Framework -> phantomrobot
Motivos relevantes para considerar o uso de Headless Browsers nos seus testes seriam:
Feedback mais rápido (lembram do post Melhorando sua Estratégia de Testes Automatizados, no qual enfatizei bastante a importância do tempo de build e feedback?)
Ideal para usar em sua suite de Smoke Tests
Ótimo para Integração Contínua
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.
Para "instalar", basta:
Baixar a última versão do PhantomJS
Extrair o .zip e adicionar o diretório do PhantomJS na sua variável PATH
Usar uma versão do Selenium WebDriver igual ou superior à 2.33.0.
A versão 2.40.0 está com uma release mais recente do GhostDriver, a 1.1.0 (lembrando que a última release é a 1.1.1). Recomendo acompanhar a lista de releases do projeto no GitHub e também o changelog das versões do Selenium WebDriver.
A página do GhostDriver no GitHub explica, de forma simples: como fazer o setup, como utilizar o GhostDriver com o Selenium Grid, como informar as dependências do projeto no Maven ou Gradle, dentre outras informações.
Para criar uma nova instância do GhostDriver:
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:
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");
dcaps.setCapability("phantomjs.cli.args", argumentosPhantomJS);
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;
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'
"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! :)