HtmlUnit 简介
1. 简介
在本文中,我们将介绍 HtmlUnit,它允许我们使用 JAVA API 以编程方式与 HTML 站点进行交互和测试。
2. 关于HtmlUnit
HtmlUnit 是一个无 GUI 的浏览器——一种旨在以编程方式而不是由用户直接使用的浏览器。
该浏览器支持 JavaScript(通过Mozilla Rhino 引擎),甚至可以用于具有复杂 AJAX 功能的网站。所有这些都可以通过模拟典型的基于 GUI 的浏览器(如 Chrome 或 Firefox)来完成。
HtmlUnit 这个名字可能会让你认为它是一个测试框架,但是虽然它绝对可以用于测试,但它可以做的远不止这些。
它还被集成到 Spring 4 中,可以与 Spring MVC 测试框架无缝结合使用。
3. 下载和Maven依赖
HtmlUnit 可以从 SourceForge 或官方网站 下载。此外,您可以将其包含在您的构建工具(如 Maven 或 Gradle 等)中,如您在此处 所见。例如,这是您当前可以包含在项目中的 Maven 依赖项:
<dependency>
<groupId>net.sourceforge.htmlunit</groupId>
<artifactId>htmlunit</artifactId>
<version>2.23</version>
</dependency>
最新版本可以在这里 找到。
4. 网页测试
您可以通过多种方式测试 Web 应用程序——其中大部分我们在网站上一次或多次地介绍过。
使用 HtmlUnit,您可以直接解析站点的 HTML,与普通用户从浏览器进行交互,检查 JavaScript 和 CSS 语法,提交表单并解析响应以查看其 HTML 元素的内容。所有这一切,使用纯 Java 代码。
让我们从一个简单的测试开始:创建一个WebClient并获取www.blogdemo.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_whenEnteringBlogdemo_thenPageTitleIsOk()
throws Exception {
HtmlPage page = webClient.getPage("/");
Assert.assertEquals(
"Blogdemo | Java, Spring and Web Development tutorials",
page.getTitleText());
}
如果我们的网站存在 JavaScript 或 CSS 问题,您会在运行该测试时看到一些警告或错误。你应该纠正它们。
有时,如果您知道自己在做什么(例如,如果您发现唯一的错误来自不应修改的第三方 JavaScript 库),您可以防止这些错误使您的测试失败,调用setThrowExceptionOnScriptError(false):
@Test
public void givenAClient_whenEnteringBlogdemo_thenPageTitleIsCorrect()
throws Exception {
webClient.getOptions().setThrowExceptionOnScriptError(false);
HtmlPage page = webClient.getPage("/");
Assert.assertEquals(
"Blogdemo | Java, Spring and Web Development tutorials",
page.getTitleText());
}
5.网页抓取
您不需要仅将 HtmlUnit 用于您自己的网站。毕竟,它是一个浏览器:您可以使用它来浏览您喜欢的任何网络,根据需要发送和检索数据。
从网站获取、解析、存储和分析数据的过程称为网络抓取,HtmlUnit 可以帮助您完成获取和解析部分。
前面的示例显示了我们如何进入任何网站并在其中导航,检索我们想要的所有信息。
例如,让我们转到 Blogdemo 的完整文章存档,导航到最新文章并检索其标题(第一个<h1>标记)。对于我们的测试,这就足够了;但是,如果我们想存储更多信息,例如,我们也可以检索标题(所有<h2>标签),从而对文章的内容有一个基本的了解。
通过 ID 获取元素很容易,但通常,如果您需要查找元素,使用 XPath 语法会更方便。HtmlUnit 允许我们使用它,所以我们会。
@Test
public void givenBlogdemoArchive_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<HtmlHeading1> h1
= (List<HtmlHeading1>) postPage.getByXPath("//h1");
Assert.assertTrue(h1.size() > 0);
}
首先注意如何——在这种情况下,我们对 CSS 和 JavaScript 不感兴趣,只想解析 HTML 布局,所以我们关闭了 CSS 和 JavaScript。
在真正的网络抓取中,您可以以h1和h2标题为例,结果将是这样的:
Java Web Weekly, Issue 135
1. Spring and Java
2. Technical and Musings
3. Comics
4. Pick of the Week
您可以检查检索到的信息是否确实与 Blogdemo 中的最新文章相对应:
6. AJAX 怎么样?
AJAX 功能可能是个问题,因为 HtmlUnit 通常会在 AJAX 调用完成之前检索页面。很多时候,您需要他们完成正确测试您的网站或检索您想要的数据。有一些方法可以处理它们:
- 您可以使用webClient.setAjaxController(new NicelyResynchronizingAjaxController())。这会重新同步从主线程执行的调用,并且这些调用是同步执行的,以确保有一个稳定的状态可供测试。
- 进入 Web 应用程序的页面时,您可以等待几秒钟,以便有足够的时间让 AJAX 调用完成。为此,您可以使用webClient.waitForBackgroundJavaScript(MILLIS)或webClient.waitForBackgroundJavaScriptStartingBefore(MILLIS)。您应该在检索页面之后,但在使用它之前调用它们。
- 您可以等到与执行 AJAX 调用相关的某些预期条件得到满足。例如:
for (int i = 0; i < 20; i++) {
if (condition_to_happen_after_js_execution) {
break;
}
synchronized (page) {
page.wait(500);
}
}
- 不要创建新的 WebClient()(默认为受最佳支持的 Web 浏览器),而是尝试其他浏览器,因为它们可能更适合您的 JavaScript 或 AJAX 调用。例如,这将创建一个使用 Chrome 浏览器的 webClient:
WebClient webClient = new WebClient(BrowserVersion.CHROME);
7. Spring 示例
如果我们正在测试我们自己的 Spring 应用程序,那么事情会变得容易一些——我们不再需要一个正在运行的服务器。
让我们实现一个非常简单的示例应用程序:只是一个带有接收文本的方法的控制器和一个带有表单的 HTML 页面。用户可以在表单中输入文本,提交表单,文本将显示在该表单下方。
在这种情况下,我们将为该 HTML 页面使用Thymeleaf 模板(您可以在此处 查看完整的 Thymeleaf 示例):
@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);
}
}
这里的关键是使用WebApplicationContext中的MockMvcWebClientBuilder构建WebClient对象。使用WebClient,我们可以获得导航的第一页(注意它是如何由localhost提供的),然后从那里开始浏览。
如您所见,测试解析表单输入消息(在 ID 为“message”的字段中),提交表单,并在新页面上断言接收到的文本(ID 为“received”的字段)是与我们提交的文本相同。