作者:吴香礼

Email:wxl901018@163.com

QQ:1060394242

本文禁止用于商业用途

1.     简介

htmlunit是一款开源的java页面分析工具,读取页面后,可以有效的使用htmlunit分析页面上的内容。项目可以模拟浏览器运行,被誉为java浏览器的开源实现。这个没有界面的浏览器,运行速度也是非常迅速的。

2.     起步

l  介绍

在依赖项页面列出的所有Jar文件,必须添加到CLASSPATH。

com.gargoylesoftware.htmlunit.WebClient类是起步的关键,它是一个仿真浏览器,其将会被用来执行所有测试。

大多数的测试单元将在一个像Junit的框架下进行,所以假设所有的例子我们都这样进行。

在第一个例子中,我们创建一个web客户端,使用该客户端来下载HtmlUnit网站的首页。然后我们确保这个页面有正确的标题。注意,根据返回数据的的内容类型,getPage()可以返回不同类型的页面。在这个例子中,我们期望text/html内容类型的页面,以致我们可以将结果存于com.gargoylesoftware.htmlunit.html.HtmlPage。

@Test

publicvoid homePage() throws Exception {

//创建web客户端

final WebClient webClient = newWebClient();

//设置链接地址

final HtmlPage page =webClient.getPage("http://htmlunit.sourceforge.net");

//断言页面的标题,不成立时报错

Assert.assertEquals("HtmlUnit -Welcome to HtmlUnit", page.getTitleText());

//获取页面的Xml代码

final String pageAsXml = page.asXml();

//断言页面包含

Assert.assertTrue(pageAsXml.contains("<bodyclass=\"composite\">"));

//获取页面的文本内页

final String pageAsText = page.asText();

//断言页面内容包含

Assert.assertTrue(pageAsText.contains("Supportfor the HTTP and HTTPS protocols"));

//关闭所有窗口

webClient.closeAllWindows();

}

l  模仿特定浏览器

有时你想模仿一个特殊的浏览器,这可以通过WebClient构造函数的com.gargoylesoftware.htmlunit.BrowserVersion参数实现,其中已经提供一些常见浏览器的常量,但是,你可以通过BrowserVersion的实例说明创建你自己拥有的特殊版本。

@Test

publicvoid homePage_Firefox() throws Exception {

//创建并指定web客户端的类型,在此为FireFox

final WebClient webClient = new WebClient(BrowserVersion.FIREFOX_17);

final HtmlPage page =webClient.getPage("http://htmlunit.sourceforge.net");

Assert.assertEquals("HtmlUnit -Welcome to HtmlUnit", page.getTitleText());

webClient.closeAllWindows();

}

指定这个BrowserVersion会改变用户代理发送到服务器的报头,也会改变一些JavaScript的行为。

l  查找特殊元素

一旦有一个HtmlPage的引用,你就可以通过其中的get方法或者使用Xpath搜索特殊的HtmlElement。下面是一个通过ID查找div和通过name获取或取一个<a>的超链接的例子。

@Test

publicvoid getElements() throws Exception {

final WebClient webClient = newWebClient();

//将http://some_url改为一个网址,如http://www.baidu.com/

final HtmlPage page =webClient.getPage("http://some_url");

//将some_div_id改为特定ID。需根据page代码而定,如百度可以将id设为wrapper

final HtmlDivision div =page.getHtmlElementById("some_div_id");

//将anchor_name改为指定的name,也许因页面代码而定,如百度可以将其设为tj_settingicon

final HtmlAnchor anchor =page.getAnchorByName("anchor_name");

webClient.closeAllWindows();

}

在更复杂的搜索中,Xpath是建议方法,其可以在W3Schools教程中发现学习。

@Test

publicvoid xpath() throws Exception {

final WebClient webClient = newWebClient();

final HtmlPage page =webClient.getPage("http://htmlunit.sourceforge.net");

//获取所有div的列表

final List<?> divs =page.getByXPath("//div");

//获取有name属性为John的div

final HtmlDivision div = (HtmlDivision)page.getByXPath("//div[@name='John']").get(0);

webClient.closeAllWindows();

}

l  使用代理服务器

WebClient的最后一个构造函数允许你指定代理服务器信息,在这种情况下,你的连接需要经过代理服务器。

@Test

publicvoid homePage_proxy() throws Exception {

//需指定myProxyPort

final WebClient webClient = newWebClient(BrowserVersion.FIREFOX_10, "myproxyserver",

myProxyPort);

//设置代理的用户名和密码

final DefaultCredentialsProvidercredentialsProvider = (DefaultCredentialsProvider)                               webClient.getCredentialsProvider();

credentialsProvider.addCredentials("username","password");

final HtmlPage page =webClient.getPage("http://htmlunit.sourceforge.net");

Assert.assertEquals("HtmlUnit -Welcome to HtmlUnit", page.getTitleText());

webClient.closeAllWindows();

}

指定这个BrowserVersion会改变用户代理发送到服务器的报头,也会改变一些JavaScript的行为。

l  提交表单

我们经常想改变一个表单的值并提交回服务器。下面的例子将示例你如何去做。

@Test

publicvoid submittingForm() throws Exception {

final WebClient webClient = new WebClient();

// 获取第一页,修改http://some_url为指定连接

final HtmlPage page1 =webClient.getPage("http://some_url");

// 获取我们需要处理的表单,并在里面找到提交按钮和我们需要修改的域

//指定myform为特定name,如百度首页可为f

final HtmlForm form =page1.getFormByName("myform");

//指定submitbutton为指定Id,如百度首页可为su

final HtmlSubmitInput button =form.getInputById("submitbutton");

//获取修改域,如百度可将userid改为wd

final HtmlTextInput textField =form.getInputByName("userid");

// 修改域的值

textField.setValueAttribute("root");

// 通过点击按钮提交表单并获取返回页面

final HtmlPage page2 = button.click();

webClient.closeAllWindows();

}

3.     使用Keyboards

对于给定的WebClient,在任何时候焦点定在一个元素上,没有必要把焦点定在WebClient里的所以元素上。这里有几种方法,可以将焦点从一个元素移动到另一个元素上。最简单的方法就是调用HtmlPage.setFocusedElement(HtmlElement),这种方法将会把焦点从任何获得焦点的当前元素移动到指定的新组件上。沿途将会触发已经定义的所有onfocus和onblur方法。

调用HtmlPage.getFocusedElement()可以确定当前获得焦点的元素。通过tab键模拟键盘导航,你可以调用HtmlPage.tabToNextElement()和HtmlPage.tabToPreviousElement(),通过定义的tab顺序循环向前或向后。这个tab顺序可以通过在很多元素的tabindex属性定义,像HTML规范那样。

HtmlPage.getTabbableElements()方法返回所有可标签化的元素已定义的顺序的列表,可以通过此方法查询已定义的tab顺序。

快捷键可以调用键盘的助记符,其可以方法HtmlPage.pressAccessKey(char)模拟。为了使用特殊的键,你可以使用htmlElement.type(int)方法,参数为KeyboardEvent.DOM_VK_PAGE_DOWN。

最后需要断言每一个可标签化的元素都定义了tabindex属性,可以通过WebAssert.assertAllTabIndexAttributesSet()实现。

4.     使用Table

l  简单表格例子

第一组简单的例子将会是用下面的html文件。

<html><head><title>Tablesample</title></head><body>

<table id="table1">

<tr>

<th>Number</th>

<th>Description</th>

</tr>

<tr>

<td>5</td>

<td>Bicycle</td>

</tr>

</table>

</body></html>

下面这个例子将展示所有的行和单元格是如何迭代的。

finalHtmlTable table = page.getHtmlElementById("table1");

for(final HtmlTableRow row : table.getRows()) {

System.out.println("Found row");

for (final HtmlTableCell cell :row.getCells()) {

System.out.println("   Found cell: " + cell.asText());

}

}

下面这个例子将会展示如何访问指定的行和列。

finalWebClient webClient = new WebClient();

finalHtmlPage page = webClient.getPage("http://foo.com");

//通过id获取表格

finalHtmlTable table = page.getHtmlElementById("table1");

//打印第一行第二列

System.out.println("Cell(1,2)=" + table.getCellAt(1,2));

l  复杂表格例子

这一组例子将会使用更复杂的表格,表格中包括表头、脚注和主体以及一个标题。

<html><head><title>Tablesample</title></head><body>

<table id="table1">

<caption>My complextable</caption>

<thead>

<tr>

<th>Number</th>

<th>Description</th>

</tr>

</thead>

<tfoot>

<tr>

<td>7</td>

<td></td>

</tr>

</tfoot>

<tbody>

<tr>

<td>5</td>

<td>Bicycle</td>

</tr>

</tbody>

<tbody>

<tr>

<td>2</td>

<td>Tricycle</td>

</tr>

</tbody>

</table>

</body></html>

HtmlTableHeader,HtmlTableFooter和HtmlTableBody部分是以行分组的。其最多只能有一个头和一个页脚,但可能会有不止一个的身体。行中包含的都可以通过getRows()访问。

finalHtmlTableHeader header = table.getHeader();

finalList<HtmlTableRow> headerRows = header.getRows();

finalHtmlTableFooter footer = table.getFooter();

finalList<HtmlTableRow> footerRows = footer.getRows();

for(final HtmlTableBody body : table.getBodies()) {

final List<HtmlTableRow> rows =body.getRows();

...

}

每个表格可以选择性的有一个标题,用于说明。

finalString caption = table.getCaptionText();

5.     使用Frames

l  例一

可以通过HtmlPage.getFrames()获取页面里面的<frame>或<iframe>元素,假设你有下面的页面。

<html>

<body>

<iframe src="two.html">

</body>

</html>

你可以使用下面的代码获取上述页面的内容。

finalList<FrameWindow> window = page.getFrames();

finalHtmlPage pageTwo = (HtmlPage) window.get(0).getEnclosedPage();

l  例二

这个例子是以API文档的导航页作为需要的页面的。

finalWebClient client = new WebClient();

finalHtmlPage mainPage = client.getPage("http://htmlunit.sourceforge.net/apidocs/index.html");

为了获取页面的第一个frame(在左上角)并点击第六个连接。

finalHtmlPage packageListPage = (HtmlPage)mainPage.getFrames().get(0).getEnclosedPage();

packageListPage.getAnchors().get(5).click();

为了获取页面中名为packageFrame的frame(在左下)并点击第二个链接。

finalHtmlPage pakcagePage = (HtmlPage)        mainPage.getFrameByName("packageFrame").getEnclosedPage();

pakcagePage.getAnchors().get(1).click();

为了获取页面中名为classFrame的frame(在右边)。

finalHtmlPage classPage = (HtmlPage)mainPage.getFrameByName("classFrame").getEnclosedPage();

6.     使用Windows

所有页面都包含在WebWindows对象里,在里面一个顶层的窗口(TopLevelWindows)代表一个实际的浏览器,一个HtmlFrame代表一个<frame>元素,或者一个HtmlInlineFrame代表一个<Iframe>元素。

当一个WebClient第一次实例化,一个顶层窗口就被创建。你可认为他就是通过浏览器展示的第一个窗口。调用WebClient.getPage(WebWindow, WebRequest)方法将会加载一个新的页面进入该窗口。

JavaScript的open()方法在加载页面到其他窗口时被使用到,新的WebWindows对象通过这种方法将会被自动创建。

l  WebWindowsEvents

如果你想在窗口被创建或者页面加载时得到通知,你需要为WebClient注册一个WebWindowsListener,可以通过方法WebClient.addWebWindowListener(WebWindowListener)实现。当一个窗口被JavaScript或者通过WebClient打开时,一个WebWindowsEvent将被触发,该事件将会传入WebWindowListener.webWindowOpened(WebWindowEvent)方法。注意,如果这时窗口没有任何内容加载,在实践中新和旧的页面将被置为null。如果在创建窗口时指定了URL,页面将会被加载而且其他事件将被触发。

一个新的页面被加载到指定窗口时,一个WebWindowsEvent将被触发件将会传入WebWindowListener.webWindowContentChanged(WebWindowEvent)方法。

7.     使用Javascript

l  介绍

我们收到的常见问题是,我们该怎样测试我们的JavaScript。使用JavaScript真的没有什么特别的,它是一个自动的过程,你只需要用getPage()获取页面,找到元素,然后调用click(),最后检查结果就可以了。复杂的JavaScript测试的库包含在HtmlUnit的测试基地里,你可以通过链接http://sourceforge.net/p/htmlunit/code/HEAD/tree/找到,也许你可以发现有用的想法。

l  使用Document.write()

例如说,我们有一个页面包含JavaScript,脚本动态向页面写内容。下面的html文本将动态生成五个文本域并将其插入表格。被一个文本域通过追加的索引到字符串获得唯一的名字。

<html><head><title>Tablesample</title></head><body>

<form action='/foo' name='form1'>

<table id="table1">

<scripttype="text/javascript">

for (i = 1; i <= 5; i++) {

document.write("<tr><td>" + i

+ "</td><td><input name='textfield" + i

+ "' type='text'></td></tr>");

}

</script>

</table></form>

</body></html>

我们可能想测试五个文本域是否被创建,我们可以这样做。

@Test

publicvoid documentWrite() throws Exception {

final WebClient webClient = new WebClient();

final HtmlPage page =webClient.getPage("http://myserver/test.html");

final HtmlForm form =page.getFormByName("form1");

for (int i = 1; i <= 5; i++) {

final String expectedName ="textfield" + i;

Assert.assertEquals(

"text",

form.<HtmlInput>getInputByName(expectedName).getTypeAttribute());

}

}

我们可能想检查差一错误(off-by-one,比指定范围大一或小一)错误,确保不会创建textfield0”或textfield6。尝试获取不存在的元素将会抛出异常,因此我们可以添加这些在前期测试的尾部。

try{

form.getInputByName("textfield0");

fail("Expected anElementNotFoundException");

}catch(final ElementNotFoundException e) {

// Expected path

}

try{

form.getInputByName("textfield6");

fail("Expected anElementNotFoundException");

}catch(final ElementNotFoundException e) {

// Expected path

}

l  等待警告

通常你想查看JavaScript触发的警告。

<html><head><title>Alertsample</title></head>

<bodyοnlοad='alert("foo");'>

</body></html>

AlertHandler可以跟踪警告,无论什么时候,当JavaScript的alert函数被调用时,AlertHandler将被调用。在下面的测试中,我们注册一个警告处理者(Alert Handler),通过它将所有警告消息存入列表。当页面加载完全,我们比较收集到的警告消息列表和预期的消息的列表,确保他们一致。

@Test

publicvoid alerts() throws Exception {

final WebClient webClient = newWebClient();

final List collectedAlerts = newArrayList();

webClient.setAlertHandler(newCollectingAlertHandler(collectedAlerts));

// 因为我们没有实际操作页面,我们不分配给一个变量——这足以知道它加载。

//可以将url改为你有的页面链接

webClient.getPage("http://tciludev01/test.html");

final List expectedAlerts =Collections.singletonList("foo");

Assert.assertEquals(expectedAlerts,collectedAlerts);

}

l  提示,确认和状态栏信息

提示,确认和状态栏消息的处理和警告的处理一样。你注册一个适当的处理者(handler),当那些方法被调用时他将获得通知。查看WebClient.setPromptHandler(),WebClient.setConfirmHandler() 和WebClient.setStatusHandler() 的详细信息可以参考API Doc。

l  事件处理

大多数的事件处理已被实现,onload, onclick, ondblclick,onmouseup, onsubmit, onreadystatechange, ...它们将会在适当的时候被触发就像在真正的浏览器那样。如果你测试不支持的事件,你可以直接通过ScriptEngine调用它。注意,脚本引擎(ScriptEngine)是公共可访问的,我们不推荐你直接使用,除非你没有其他选择。友好的用户界面允许用户点击元素和改变焦点。

8.     使用ActiveX

l  动机

虽然HtmlUnit是纯java实现的仿真浏览器,但是,在某些特定的情况下,特殊平台的功能需求集成其他的库,ActiveX就是他们中的一个。

Windows的IE可以运行任意的ActiveX组件(如果是用户信任的网站,可有目的的降低安全等级)。HtmlUnit或者IE都没有任何控制ActiveX的运行行为,所以当你是用该功能时你必须小心。

l  Jacob

当前的实现是依赖Jacob的,由于它有dll的依赖项,所以在Maven仓库没有更新。依赖项是可选的,例如,Jacob jar对于编译或平时HtmlUnit的使用的无须的。

为了使用Jacob,需要将Jacob.jar添加到CLASSPATH,而且将.dll添加到java.library.path(jdk安装目录的bin或lib文件夹),确保下面的代码可以工作。

finalActiveXComponent activeXComponent = newActiveXComponent("InternetExplorer.Application");

finalboolean busy = activeXComponent.getProperty("Busy").getBoolean();

System.out.println(busy);

l  允许HtmlUnit使用ActiveX

唯一需要做的事就是设置WebClient的属性。

webClient.getOptions().setActiveXNative(true);

9.     写在最后

本文是在参考HtmlUnit官网内容的基础上,翻译修改而来,由于能力有限,欢迎指正其中存在的问题。

本文只作为HtmlUnit的入门级教程,看完本文相信你也对HtmlUnit有了初步的了解,如需进一步研究,建议读者自行参考它的开发文档。另外,由于HtmlUnit面向的对象为Html,所以对它的使用需要一定的Html基础,如需参考Html的知识建议参考参考文献[3]。最后,要想真正提升知识的理解需要多动手的同时,好要善于使用网络资源,学会查找资料。

最后希望看到本文的读者可以一起进步。

10.参考文献

[1] sourceforge.net. HtmlUnit[Z].http://htmlunit.sourceforge.net/

[2] 百度百科.HtmlUnit[Z].http://baike.baidu.com/view/3724597.htm

[3] w3school.HTML[Z].http://www.w3school.com.cn/html/index.asp

[4] w3school.XPath[Z].http://www.w3school.com.cn/xpath/

HtmlUnit入门教程相关推荐

  1. 【转】HtmlUnit入门教程

    转载出处:http://blog.csdn.net/wxl901018/article/details/44133873 1. 简介 htmlunit是一款开源的Java页面分析工具,读取页面后,可以 ...

  2. Kafka入门教程与详解

    1 Kafka入门教程 1.1 消息队列(Message Queue) Message Queue消息传送系统提供传送服务.消息传送依赖于大量支持组件,这些组件负责处理连接服务.消息的路由和传送.持久 ...

  3. 【CV】Pytorch一小时入门教程-代码详解

    目录 一.关键部分代码分解 1.定义网络 2.损失函数(代价函数) 3.更新权值 二.训练完整的分类器 1.数据处理 2. 训练模型(代码详解) CPU训练 GPU训练 CPU版本与GPU版本代码区别 ...

  4. python tornado教程_Tornado 简单入门教程(零)——准备工作

    前言: 这两天在学着用Python + Tornado +MongoDB来做Web开发(哈哈哈这个词好高端).学的过程中查阅了无数资料,也收获了一些经验,所以希望总结出一份简易入门教程供初学者参考.完 ...

  5. python向量计算库教程_NumPy库入门教程:基础知识总结

    原标题:NumPy库入门教程:基础知识总结 视学算法 | 作者 知乎专栏 | 来源 numpy可以说是 Python运用于人工智能和科学计算的一个重要基础,近段时间恰好学习了numpy,pandas, ...

  6. mysql query browswer_MySQL数据库新特性之存储过程入门教程

    MySQL数据库新特性之存储过程入门教程 在MySQL 5中,终于引入了存储过程这一新特性,这将大大增强MYSQL的数据库处理能力.在本文中将指导读者快速掌握MySQL 5的存储过程的基本知识,带领用 ...

  7. python tensorflow教程_TensorFlow入门教程TensorFlow 基本使用T

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 TensorFlow入门教程 TensorFlow 基本使用 TensorFlow官方中文教程 TensorFlow 的特点: 使用图 (graph) 来 ...

  8. air调用java,AIR2.0入门教程:与Java应用交互

    在之前的一篇文章中,我介绍了如何使用AIR2.0新增的NativeProcess类与本地进程进行交互和通讯,在那个例子里面我们使用了C++ 的代码,实际上只要是基于命令行的标准输入输出,AIR2.0的 ...

  9. 【Arduino】开发入门教程【一】什么是Arduino

    Arduino Arduino 是一款便捷灵活.方便上手的开源电子原型平台,包含硬件(各种型号的arduino板)和软件(arduino IDE).它适用于艺术家.设计师.爱好者和对于"互动 ...

最新文章

  1. 【数据安全案例】车管信息再遭窃取,数据安全缺乏保障
  2. android从放弃到精通 第七天 tomorrow
  3. IAR 不能使用go to 的解决方法
  4. Spring Boot入门(9)网页版计算器
  5. 关于质量标准化的思考和实践
  6. 总结深度学习各种网络结构【更新中...】
  7. Oracle数据库对象 序列
  8. HTML5缓存之 WebStorage
  9. 2021-06-18html基本标签学习
  10. jemter java请求后置_jmeter完成一个java请求-本机调用
  11. PDF格式分析(六十五) Text 文字——字体数据结构
  12. 请问,非计算机专业,只为软考中级,哪一种最容易过?
  13. 中华石杉-- --搜索引擎的笔记
  14. 从身边的移动支付说起
  15. 网站被劫持怎么办,怎么解决?
  16. 【ZYNQ】中断机制介绍(一)
  17. 摘录互联网企业的优秀企业文化集萃
  18. Linux进程5:exec族函数(execl, execlp, execle, execv, execvp, execvpe)总结及exec配合fork使用
  19. metaball公式_Houdini 节点解释
  20. 大班科学计算机的发明应用教案,大班科学活动神奇的圈教案

热门文章

  1. 2021美赛写作(一)
  2. C++之面向对象(上)
  3. App关键字(100字符)优化的方法
  4. c# 计算圆锥的体积_求帮忙写一道c#题目 :编写一个c#程序计算球、圆柱和圆锥的表面积和体积。...
  5. 做一个九宫格诗词答题小程序 (二)倒计时功能实现
  6. Win10安装DNW相关驱动
  7. 错误记录----javac错误:javac不是内部或外部命令 也不是可运行的程序
  8. Jina文章转载:多模态AI的范式变革多模态AI总结(2022年COLING会议)
  9. 5G标准核心内容:R15+R16(内含赠书福利)
  10. SQL Server 扩展秘钥管理(EKM)