Pontos Principais
- Os navegadores headless são executados sem uma interface do usuário.
- O PhantomJS não é mais suportado.
- O JBrowser Driver é uma opção de baixa sobrecarga para o Java 8.
- Todos os atuais drivers do Selenium headless do Java requerem a instalação de um navegador real se precisar do suporte a Java 11.
O Selenium é uma poderosa ferramenta conhecida para testes automatizados em navegadores web. Embora o driver Selenium Web ofereça suporte a todos os principais navegadores, nem sempre se deseja os custos de testes usando um navegador real. Navegadores headless para o resgate! Os exemplos neste artigo estão em um repositório do GitHub. Tudo foi executado com o JUnit 5 e o Maven. O suporte a Java 11 vs Java 8 é indicado para cada exemplo.
Quais são os benefícios de um navegador headless?
Os navegadores headless funcionam sem uma interface de usuário (IU). Um grande benefício de usar um para teste é o desempenho - já que os navegadores headless não têm uma interface do usuário, eles são mais rápidos do que os navegadores reais.
Há outra vantagem de alguns navegadores headless: dependências. Ao testar em um servidor de integração contínua como o Jenkins, a máquina pode não ter um navegador real instalado. E, dependendo do seu ambiente, talvez não tenha permissões para instalar um.
Um dos outros navegadores headless que exigem um navegador "real" para ser instalado é ótimo para desenvolvimento. Por exemplo, o Chrome e o Firefox têm a opção de executar no modo headless. É realmente útil ao depurar um script do Selenium para desativar temporariamente o modo headless e assistir a execução do programa. Dessa forma, é possível observar visualmente onde as coisas ficam malucas.
HtmlUnitDriver - o driver headless inicial
No passado, o Selenium veio com um driver embutido chamado HtmlUnitDriver. Embora esse driver ainda seja suportado, agora é uma dependência separada e, sem surpresa, usa um framework Html Unit. Antes dos aplicativos de página única e em grande parte páginas baseadas em AJAX, este driver foi uma excelente escolha. Caso deseje, tem a capacidade de escolher executar a página JavaScript, ele é executado na memória e é muito rápido. Ainda é uma boa escolha para páginas Web com uma boa quantidade de dados HTML nelas.
O código a seguir mostra como executar um teste básico que usa o Selenium com o HtmlUnitDriver. Funciona porque a página inicial da InfoQ foi projetada para funcionar sem JavaScript. Este exemplo está disponível nas versões Java 8 e Java 11 no repositório GitHub.
package com.infoq.selenium;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.htmlunit.HtmlUnitDriver;
import java.util.Set;
import java.util.stream.Collectors;
public class HtmlUnitSeleniumIT {
protected WebDriver driver;
// ----------------------------------------------------
@BeforeEach
public final void connect() {
driver = new HtmlUnitDriver();
//driver.setJavascriptEnabled(true);
}
@AfterEach
public final void closeDriver() {
if (driver != null) {
driver.quit();
}
}
@Test
void qconDates() {
driver.get(“https://www.infoq.com“);
Set<String> newYorkCity = driver.findElements(By.className(“qcon”))
.stream()
.map(element -> element.getAttribute(“innerText”))
.filter(city -> city.trim().startsWith(“New York”))
.collect(Collectors.toSet());
assertEquals(1, newYorkCity.size(), “New York is an upcoming city”);
}
}
No entanto, descomentar a linha que habilita o JavaScript é uma história diferente. Depois de cuspir uma pilha inteira de avisos de JavaScript, ele falha com EcmaError: TypeError: Não é possível chamar o método "then" do indefinido.
Como muitas páginas não são carregadas sem JavaScript, isso mostra a necessidade de um driver headless com melhor suporte ao JavaScript
PhantomJS
Por muitos anos, o PhantomJS foi uma ótima escolha. Era leve, headless e tinha excelente suporte ao JavaScript. No entanto, em abril de 2017, o mantenedor deixou o cargo e, em março de 2018, o projeto foi formalmente abandonado. Tenho saudade.
Ao ler o anúncio e os comentários, a intenção era claramente mudar para o Chrome Driver. Eu não recomendo usar o PhantomJS para nada novo. Além de não ser suportado, mudei dois dos meus projetos para o Chrome Driver, porque o PhantomJS não manipula o JavaScript bem o suficiente para as bibliotecas JavaScript mais atuais. Como o Chrome Driver usa um navegador real, isso não é problema para ele.
Chrome Driver
O Chrome oferece um modo headless, que funciona bem no geral. A maior desvantagem é que é preciso instalar o Chrome. Não é preciso uma interface do usuário, mas a instalação de um software nem sempre é possível.
O Chrome Driver também requer que um executável seja baixado. Eu trapaceei um pouco aqui. Eu mantenho o executável no mesmo diretório que o projeto (ou em um repositório binário e copio para o espaço de trabalho). Eu, então, tenho o próprio teste Java para definir as permissões. Da mesma forma, tenho o teste Java para definir a propriedade do sistema dentro do processo Java para esse local. Um pouco trapaceiro, eu sei, mas mantém quase tudo contido. "Quase" porque ainda requer que o próprio Chrome seja instalado. Isso funciona muito bem em meus projetos pessoais. Ao compartilhar códigos com outras pessoas, ele se desfaz porque também precisam fazer o download do executável.
Existem as versões Java 8 e Java 11 no repositório. Ambos exigem que baixem o executável e o substitua no diretório chrome-driver.
package com.infoq.selenium;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import static org.junit.jupiter.api.Assertions.*;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Set;
import java.util.stream.Collectors;
public class ChromeSeleniumIT {
private static final boolean HEADLESS = true;
private static final String CHROME_DRIVER_DIRECTORY = “chrome-driver”;
protected WebDriver driver;
// ----------------------------------------------------
@BeforeEach
public final void connect() {
Path chrome = Paths.get(CHROME_DRIVER_DIRECTORY + “/chromedriver”);
chrome.toFile().setExecutable(true);
System.setProperty(“webdriver.chrome.driver”, chrome.toAbsolutePath().toString());
ChromeOptions chromeOptions = new ChromeOptions();
if (HEADLESS) {
chromeOptions.addArguments(“--headless”);
}
driver = new ChromeDriver(chromeOptions);
// https://github.com/seleniumhq/selenium-google-code-issue-archive/issues/27
((JavascriptExecutor) driver).executeScript(“window.alert = function(msg) { }“);
((JavascriptExecutor) driver).executeScript(“window.confirm = function(msg) { }“);
}
@AfterEach
public final void closeDriver() {
if (driver != null) {
driver.quit();
}
}
@Test
void qconDates() {
driver.get(“https://www.infoq.com”);
Set<String> newYorkCity = driver.findElements(By.className(“qcon”))
.stream()
.map(element -> element.getAttribute(“innerText”))
.filter(city -> city.trim().startsWith(“New York”))
.collect(Collectors.toSet());
assertEquals(1, newYorkCity.size(), “New York is an upcoming city”);
}
}
As duas linhas de script de execução são para contornar um problema com prompts em outro aplicativo que estou testando. Eu instruí o driver a ignorá-los. (Embora não seja necessário aqui, foi suficientemente doloroso para descobrir que eu o uso em todos os lugares que eu uso o Chrome Driver, então eu nunca tenho que resolver esse problema!)
Outra desvantagem de usar o Chrome Driver é que ele precisa ser atualizado periodicamente para oferecer suporte a versões posteriores do Chrome.
Gecko Driver
O Chrome foi o primeiro a participar do teste de navegador headless, e esse é com o qual eu tenho mais experiência. No entanto, o Firefox também tem um modo headless, que funciona exatamente como o do Chrome. Fazendo o download do Gecko Driver e usando o selenium-firefox-driver no seu pom.xml
JBrowser Driver
Embora eu goste do Chrome Driver, ele exige que o Chrome seja instalado. Eu tenho um projeto onde eu corro um check diário para me dizer quando a Oracle muda os objetivos da certificação. O servidor em que eu o executo não tem o Chrome instalado. Recentemente, a Oracle atualizou seu site para ser mais pesado em AJAX. O PhantomJS não atendeu mais às minhas necessidades, então comecei a procurar por um driver mais moderno.
Eu encontrei o JBrowser Driver. Houve commits regulares no ano passado incluindo atualizações nas versões do Selenium. Tem uma boa licença (Apache 2.) a versão 1.0.0 saiu no ano passado. No entanto, houve versões anteriores a 1.0 por quase três anos.
A maior desvantagem do JBrowser Driver é que eles suportam apenas o Oracle JDK Java 8 atualmente. Esta é a versão do Java que não terá mais patches para usuários corporativos em janeiro de 2019 e para usuários individuais em dezembro de 2020.
Nota sobre o Java FX:
- No Java 7, o Java FX foi um download separado
- No Java 8, o Java FX fazia parte do Oracle JDK, mas não do Open JDK
- No Java 11, o Java FX está disponível gratuitamente por meio de uma dependência do Maven usando o OpenJFX
- Além disso, no Java 11, a classe Robot.java alterou os pacotes de com.sun.glass.ui para javafx.scene.robot. Isso significa que não se pode usar a versão do JavaFX do OpenJFX com o Open JDK 11 e esperar que o JBrowser Driver funcione.
O código é direto e a versão do Java 8 está no repositório do GitHub.
package com.infoq.selenium;
import com.machinepublishers.jbrowserdriver.JBrowserDriver;
import com.machinepublishers.jbrowserdriver.Settings;
import com.machinepublishers.jbrowserdriver.Timezone;
import com.machinepublishers.jbrowserdriver.UserAgent;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class JBrowserSeleniumIT {
protected WebDriver driver;
// ----------------------------------------------------
@BeforeEach
public final void connect() {
driver = new JBrowserDriver(Settings.builder()
.timezone(Timezone.AMERICA_NEWYORK)
.userAgent(UserAgent.CHROME).build());
// says 120 but is really 0
driver.manage().timeouts().pageLoadTimeout(120, TimeUnit.SECONDS);
}
@AfterEach
public final void closeDriver() {
if (driver != null) {
driver.quit();
}
}
@Test
void qconDates() {
driver.get(“https://www.infoq.com“);
Set<String> newYorkCity = driver.findElements(By.className(“qcon”))
.stream()
.map(element -> element.getAttribute(“innerText”))
.filter(city -> city.trim().startsWith(“New York”))
.collect(Collectors.toSet());
assertEquals(1, newYorkCity.size(), “New York is an upcoming city”);
}
}
Conclusão - comparando as opções
Há muitas opções de escolha para testes de navegador headless com Java e Selenium. E, como qualquer bom problema de engenharia, há vantagens e desvantagens entre as opções. Esta tabela lista suas principais escolhas. A lição é que se você precisa ser capaz de rodar um driver sem Selenium sem um navegador real instalado, você precisa ficar no Java 8 por enquanto.
Biblioteca |
JavaScript power |
Suporte Java 8 |
Suporte Java 11 |
Requer instalação do browser |
Html Unit Driver |
Baixo |
Sim |
Sim |
Não |
Phantom JS (sem suporte) |
Médio |
Sim |
Não |
Não |
Chrome Driver |
Alto |
Sim |
Sim |
Sim – Chrome |
Gecko Driver |
Alto |
Sim |
Sim |
Sim – Firefox |
JBrowser Driver |
Alto |
Sim |
Não |
Não |
Sobre a autora
Jeanne Boyarsky é uma desenvolvedora de Java e ScrumMaster em tempo parcial. Ela é coautora dos livros de certificação OCA/OCP 8 da Wiley e os atualizará para a próxima versão da certificação. Além de voluntariar-se no CodeRanch, ela orienta os programadores em uma equipe de robótica do ensino médio e ganhou um prêmio de mentoria. Jeanne falou em conferências incluindo JavaOne, QCon, DevNexus e SpringOne.