2. 关于 HtmlUnit

HtmlUnit 是一款无界面的浏览器——专为程序化操作设计,而非直接供用户使用。

这款浏览器支持 JavaScript(通过 Mozilla Rhino 引擎),即使面对复杂 AJAX 功能的网站也能轻松应对。所有操作都能模拟 Chrome 或 Firefox 等主流 GUI 浏览器的行为。

虽然名字带"Unit",但别被误导——它不仅是测试框架,功能远超测试范畴。HtmlUnit 已被集成到 Spring 4 中,可与 Spring MVC Test 框架无缝协作。

✅ 核心特点:

  • 纯 Java 实现
  • 支持 JavaScript/AJAX
  • 无需 GUI 环境
  • 可模拟主流浏览器

3. 下载与 Maven 依赖

HtmlUnit 可通过 SourceForge官网 下载,也可集成到构建工具(如 Maven/Gradle)。以下是 Maven 依赖示例:

<dependency>
    <groupId>net.sourceforge.htmlunit</groupId>
    <artifactId>htmlunit</artifactId>
    <version>2.23</version>
</dependency>

最新版本可在这里查询。

⚠️ 注意:版本号需根据实际需求更新,示例使用的是历史版本。

4. Web 测试实战

Web 应用测试方式众多,HtmlUnit 提供了一种独特方案:直接解析 HTML 页面,模拟用户交互,验证 JavaScript/CSS 语法,提交表单并解析响应——全部通过纯 Java 代码实现。

先看基础示例:创建 WebClient 访问 www.baeldung.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_whenEnteringBaeldung_thenPageTitleIsOk()
  throws Exception {
    HtmlPage page = webClient.getPage("/");
    
    Assert.assertEquals(
      "Baeldung | Java, Spring and Web Development tutorials",
        page.getTitleText());
}

若网站存在 JavaScript/CSS 问题,测试可能抛出警告或错误。建议优先修复这些问题。

当确认错误来自第三方库且无法修改时,可通过 setThrowExceptionOnScriptError(false) 忽略:

@Test
public void givenAClient_whenEnteringBaeldung_thenPageTitleIsCorrect()
  throws Exception {
    webClient.getOptions().setThrowExceptionOnScriptError(false);
    HtmlPage page = webClient.getPage("/");
    
    Assert.assertEquals(
      "Baeldung | Java, Spring and Web Development tutorials",
        page.getTitleText());
}

5. Web 爬虫应用

HtmlUnit 不仅限于自有网站测试——作为浏览器,它可导航至任何网页,按需收发数据。这种网页抓取(Web Scraping) 流程中,HtmlUnit 负责数据获取与解析。

以下示例演示:访问 Baeldung 文章归档页,提取最新文章标题(首个 <h1> 标签):

@Test
public void givenBaeldungArchive_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);
}

关键点解析:

  1. 禁用 CSS/JavaScript:仅解析 HTML 结构时提升性能
  2. 使用 XPath 定位元素:比 ID 选择更灵活
  3. 提取标题数据:通过 getByXPath 获取 <h1> 列表

实际爬虫可扩展提取更多数据,例如同时抓取 <h2> 标签:

Java Web Weekly, Issue 135
1. Spring and Java
2. Technical and Musings
3. Comics
4. Pick of the Week

数据与 Baeldung 最新文章一致(验证图): latestBaeldung

6. AJAX 处理技巧

AJAX 功能可能引发问题:HtmlUnit 常在 AJAX 调用完成前返回页面。解决方法:

6.1 同步 AJAX 调用

webClient.setAjaxController(new NicelyResynchronizingAjaxController());

将主线程调用转为同步执行,确保测试状态稳定。

6.2 显式等待

webClient.waitForBackgroundJavaScript(1000); // 等待 1 秒

或:

webClient.waitForBackgroundJavaScriptStartingBefore(1000);

在页面加载后、操作前调用,给予 AJAX 完成时间。

6.3 条件等待

for (int i = 0; i < 20; i++) {
    if (condition_to_happen_after_js_execution) {
        break;
    }
    synchronized (page) {
        page.wait(500);
    }
}

轮询检查 JS 执行后的特定条件。

6.4 切换浏览器模拟

WebClient webClient = new WebClient(BrowserVersion.CHROME);

不同浏览器对 JS/AJAX 的支持度不同,切换版本可能解决兼容性问题。

⚠️ 踩坑提示:AJAX 处理是 HtmlUnit 最易出错的部分,建议组合使用上述方法。

7. Spring 集成示例

测试自有 Spring 应用时更简单——无需启动真实服务器。以下实现一个极简应用:包含表单的 HTML 页面,用户输入文本后提交,页面显示该文本。

使用 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);     
    }
}

核心步骤:

  1. 通过 MockMvcWebClientBuilder 构建 WebClient
  2. 访问 localhost 提供的页面
  3. 填写表单(ID="message")并提交
  4. 验证响应页面文本(ID="received")

✅ 优势:集成测试无需部署,直接在测试环境中模拟完整请求流程。

8. 总结

HtmlUnit 是强大的 Web 测试工具,能模拟浏览器行为完成表单填写、提交等操作。其核心价值在于:

  • 测试场景:轻松实现 Web 应用集成测试,尤其与 Spring 4 结合后,无需服务器即可验证页面逻辑
  • 爬虫场景:自动化网页数据抓取(解析/存储/分析)
  • 扩展场景:任何需程序化操作浏览器的任务

完整代码可在 GitHub 获取。

❌ 局限性:复杂 JavaScript/AJAX 场景需额外配置,对动态渲染支持有限。


原始标题:Introduction to HtmlUnit