Introdução ao HtmlUnit
1. Introdução
Neste artigo, apresentaremos o HtmlUnit, uma ferramenta que permite, de forma simples,interact with and test an HTML site programmatically, using JAVA APIs.
2. Sobre HtmlUnit
HtmlUnit é um navegador sem interface gráfica - um navegador destinado a ser usado programaticamente e não diretamente por um usuário.
O navegador oferece suporte a JavaScript (por meio do mecanismoMozilla Rhino) e pode ser usado até mesmo para sites com funcionalidades AJAX complexas. Tudo isso pode ser feito simulando um navegador baseado em GUI típico, como Chrome ou Firefox.
O nome HtmlUnit pode levar você a pensar que é uma estrutura de teste, mas embora possa definitivamente ser usado para teste, pode fazer muito mais do que isso.
Ele também foiintegrated into Spring 4e pode ser usado perfeitamente junto com o framework Spring MVC Test.
3. Dependência de download e Maven
HtmlUnit pode serdownloaded from SourceForge ou deofficial website. Além disso, você pode incluí-lo em sua ferramenta de construção (como Maven ou Gradle, entre outros), como pode verhere. Por exemplo, esta é a dependência do Maven que você pode incluir atualmente no seu projeto:
net.sourceforge.htmlunit
htmlunit
2.23
A versão mais recente pode ser encontradahere.
4. Teste da Web
Existem várias maneiras pelas quais você pode testar um aplicativo Web - a maioria das quais abordamos aqui no site em um ponto ou outro.
Com o HtmlUnit, você pode analisar diretamente o HTML de um site, interagir com ele como um usuário normal faria no navegador, verificar a sintaxe JavaScript e CSS, enviar formulários e analisar as respostas para ver o conteúdo de seus elementos HTML. Tudo isso, usando código Java puro.
Vamos começar com um teste simples: crie umWebClient e obtenha a primeira página da navegação dewww.example.com:
private WebClient webClient;
@Before
public void init() throws Exception {
webClient = new WebClient();
}
@After
public void close() throws Exception {
webClient.close();
}
@Test
public void givenAClient_whenEnteringexample_thenPageTitleIsOk()
throws Exception {
HtmlPage page = webClient.getPage("/");
Assert.assertEquals(
"example | Java, Spring and Web Development tutorials",
page.getTitleText());
}
Você pode ver alguns avisos ou erros ao executar esse teste se nosso site tiver problemas de JavaScript ou CSS. Você deve corrigi-los.
Às vezes, se você sabe o que está fazendo (por exemplo, se você vê que os únicos erros que você tem são de bibliotecas JavaScript de terceiros que você não deve modificar), pode evitar que esses erros façam seu teste falhar, chamandosetThrowExceptionOnScriptError comfalse:
@Test
public void givenAClient_whenEnteringexample_thenPageTitleIsCorrect()
throws Exception {
webClient.getOptions().setThrowExceptionOnScriptError(false);
HtmlPage page = webClient.getPage("/");
Assert.assertEquals(
"example | Java, Spring and Web Development tutorials",
page.getTitleText());
}
5. Raspagem da web
Você não precisa usar o HtmlUnit apenas para seus próprios sites. Afinal, é um navegador: você pode usá-lo para navegar por qualquer web que desejar, enviar e recuperar dados conforme necessário.
Buscar, analisar, armazenar e analisar dados de sites é o processo conhecido como raspagem da Web e o HtmlUnit pode ajudá-lo nas partes de busca e análise.
O exemplo anterior mostra como podemos entrar em qualquer site e navegar por ele, recuperando todas as informações que queremos.
Por exemplo, vamos ao arquivo completo de artigos de exemplo, navegue até o artigo mais recente e recupere seu título (primeira tag<h1>). Para o nosso teste, isso será suficiente; mas, se quiséssemos armazenar mais informações, poderíamos, por exemplo, recuperar os cabeçalhos (todas as tags<h2>) também, tendo assim uma ideia básica do que trata o artigo.
É fácil obter elementos por seu ID, mas geralmente, se você precisar encontrar um elemento, é mais convenienteuse XPath syntax. HtmlUnit nos permite usá-lo, por isso vamos.
@Test
public void givenexampleArchive_whenRetrievingArticle_thenHasH1()
throws Exception {
webClient.getOptions().setCssEnabled(false);
webClient.getOptions().setJavaScriptEnabled(false);
String url = "/full_archive";
HtmlPage page = webClient.getPage(url);
String xpath = "(//ul[@class='car-monthlisting']/li)[1]/a";
HtmlAnchor latestPostLink
= (HtmlAnchor) page.getByXPath(xpath).get(0);
HtmlPage postPage = latestPostLink.click();
List h1
= (List) postPage.getByXPath("//h1");
Assert.assertTrue(h1.size() > 0);
}
Primeiro, observe como - nesse caso, não estamos interessados em CSS nem JavaScript e apenas queremos analisar o layout HTML; portanto, desativamos CSS e JavaScript.
Em um web scraping real, você poderia pegar, por exemplo, os títulosh1eh2, e o resultado seria algo assim:
Java Web Weekly, Issue 135
1. Spring and Java
2. Technical and Musings
3. Comics
4. Pick of the Week
Você pode verificar se as informações recuperadas correspondem ao artigo mais recente, como exemplo:
6. E quanto ao AJAX?
As funcionalidades do AJAX podem ser um problema porque o HtmlUnit geralmente recupera a página antes que as chamadas do AJAX sejam concluídas. Muitas vezes, é necessário que eles terminem para testar adequadamente seu site ou recuperar os dados desejados. Existem algumas maneiras de lidar com eles:
-
Você pode usarwebClient.setAjaxController(new NicelyResynchronizingAjaxController()). Isso ressincroniza as chamadas executadas no encadeamento principal e essas chamadas são executadas de forma síncrona para garantir que haja um estado estável a ser testado.
-
Ao entrar na página de um aplicativo Web, você pode esperar alguns segundos para que haja tempo suficiente para que as chamadas AJAX sejam concluídas. Para conseguir isso, você pode usarwebClient.waitForBackgroundJavaScript(MILLIS) ouwebClient.waitForBackgroundJavaScriptStartingBefore(MILLIS). Você deve chamá-los depois de recuperar a página, mas antes de trabalhar com ela.
-
Você pode esperar até que alguma condição esperada relacionada à execução da chamada AJAX seja atendida. Por exemplo:
for (int i = 0; i < 20; i++) {
if (condition_to_happen_after_js_execution) {
break;
}
synchronized (page) {
page.wait(500);
}
}
-
Em vez de criar umnew WebClient(), que é padronizado para o navegador da web com melhor suporte, tente outros navegadores, pois eles podem funcionar melhor com suas chamadas JavaScript ou AJAX. Por exemplo, isso criará um webClient que usa um navegador Chrome:
WebClient webClient = new WebClient(BrowserVersion.CHROME);
7. Um exemplo com Spring
Se estivermos testando nosso próprio aplicativo Spring, as coisas ficarão um pouco mais fáceis -we no longer need a running server.
Vamos implementar um aplicativo de exemplo muito simples: apenas um controlador com um método que recebe um texto e uma única página HTML com um formulário. O usuário pode inserir um texto no formulário, enviá-lo e o texto será mostrado abaixo desse formulário.
Nesse caso, usaremos um modeloThymeleaf para essa página HTML (você pode ver um exemplo completo do Thymeleafhere):
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(classes = { TestConfig.class })
public class HtmlUnitAndSpringTest {
@Autowired
private WebApplicationContext wac;
private WebClient webClient;
@Before
public void setup() {
webClient = MockMvcWebClientBuilder
.webAppContextSetup(wac).build();
}
@Test
public void givenAMessage_whenSent_thenItShows() throws Exception {
String text = "Hello world!";
HtmlPage page;
String url = "http://localhost/message/showForm";
page = webClient.getPage(url);
HtmlTextInput messageText = page.getHtmlElementById("message");
messageText.setValueAttribute(text);
HtmlForm form = page.getForms().get(0);
HtmlSubmitInput submit = form.getOneHtmlElementByAttribute(
"input", "type", "submit");
HtmlPage newPage = submit.click();
String receivedText = newPage.getHtmlElementById("received")
.getTextContent();
Assert.assertEquals(receivedText, text);
}
}
A chave aqui é construir o objetoWebClient usandoMockMvcWebClientBuilder deWebApplicationContext. Com oWebClient, podemos obter a primeira página da navegação (observe como é servido porlocalhost) e começar a navegar a partir daí.
Como você pode ver, o teste analisa o formulário digitando uma mensagem (em um campo com o ID "message"), envia o formulário e, na nova página, afirma que o texto recebido (campo com o ID "recebido") é o mesmo que o texto que enviamos.
8. Conclusão
O HtmlUnit é uma ótima ferramenta que permite testar seus aplicativos da Web com facilidade, preenchendo campos de formulários e enviando-os como se você estivesse usando a Web em um navegador.
Ele se integra perfeitamente ao Spring 4 e, junto com o framework Spring MVC Test, oferece um ambiente muito poderoso para fazer testes de integração de todas as suas páginas, mesmo sem um servidor da web.
Além disso, usando o HtmlUnit, você pode automatizar qualquer tarefa relacionada à navegação na Web, como buscar, analisar, armazenar e analisar dados (raspagem na Web).
Você pode obter o códigoover on Github.