Twitter About Home

Using HtmlUnit on .NET for Headless Browser Automation

If you subscribe to this blog, you may have noticed that I’ve been writing about test automation methods a lot lately. You could even think of it as a series covering different technical approaches:

Published Mar 30, 2010

The reason I keep writing about this is that I still think it’s very much an unsolved problem. We all want to deliver more reliable software, we want better ways to design functionality and verify implementation… but we don’t want to get so caught up in the beaurocracy of test suite maintenance that it consumes all our time and obliterates productivity.

Yet another approach

Rails developers (and to a lesser extent Java web developers) commonly use yet another test automation technique: hosting the app in a real web server, and accessing it through a fast, invisible, simulated web browser rather than a real browser. This is known as headless browser automation.

How is this beneficial?

  • It’s faster than driving a real browser. The simulated browser is just a native code library, so it’s very quick to launch and shut down, doesn’t require interprocess communication with your test suite, and doesn’t waste time physically drawing things on your screen, opening and closing pop-up windows, etc. Nonetheless, the simulated browser is full-featured: it exposes the usual HTML DOM API, runs JavaScript, evaluates CSS rules, and so on, so it’s still an effective way to specify rich client-side behaviours.
  • It’s more reliable than a real browser. Real browsers are complicated. For example, if you launch and shut down Firefox many times in rapid succession, occasionally it will fail to launch because a previous instance is still locking some file. Simulated browsers are totally independent, so don’t suffer these kinds of weirdness.
  • You can get more low-level control if you want it. For example, the simulated browser can easily offer an API to alter the HTTP headers it sends, or let you get or set the contents of its cache. A real browser wouldn’t usually make this easy.

And what drawbacks might you expect?

  • It can be harder to debug. Because you can’t physically see the browser on your desktop, it’s not as obvious what’s happening if a test fails (or passes) unexpectedly. Your debugger’s “immediate mode” will let you call the browser’s API to figure things out, but it can be a longer investigative process.
  • There’s no absolute guarantee that it faithfully replicates a real-world browser. For example, HtmlUnit can simulate multiple versions of Firefox, Internet Explorer, and Netscape, but it may not simulate every single quirk.

About HtmlUnit

HtmlUnit is a headless browser automation library for Java. It’s very well-developed and mature, as you can see from its extensive API. (Need to configure whether the user has JavaScript on or off? No problem.) It’s also the same technology that underlies Celerity, a Ruby library that exposes the Watir API but runs faster.

Unfortunately, in .NET world, we don’t have any good headless browser automation libraries like HtmlUnit that I know of. But don’t let that stop you! We do have IKVM – a near-magic way of running Java code on .NET (either by hosting a JVM on the CLR at runtime, or by a one-time conversion process that generates a native .NET assembly directly from the Java bytecode). So, why not use HtmlUnit itself?

Converting HtmlUnit to .NET

It’s surprisingly easy to get HtmlUnit, a Java library, converted into a native .NET assembly (no Java Virtual Machine needed!) using IKVM.

First, download HtmlUnit (as a compiled JAR file) from SourceForge (I’m using version 2.7), and extract all its files from the ZIP archive.

Second, download IKVM binaries from ikvm.net/SourceForge (I’m using version 0.42.0.3), and again extract all its files from the ZIP archive.

Open a command prompt, ensure you’ve added IKVM’s /bin folder to your PATH variable, change directory to HtmlUnit’s /lib folder, and then run ikvmc to convert the Java bytecode of all of the HtmlUnit JAR files into .NET bytecode. I ran this command:

ikvmc -out:htmlunit-2.7.dll *.jar

This produced a lot of warnings and a large (~10Mb) .NET assembly called htmlunit-2.7.dll. If you don’t want to bother with this process, you can download my sample project (linked below) which contains the .NET assembly I generated.

Using HtmlUnit from .NET

Once you’ve got htmlunit-2.7.dll as a .NET assembly, you can use it with any .NET unit testing library such as NUnit or XUnit. You will also need to reference a few of the IKVM runtime assemblies (I narrowed it down to six additional IKVM assemblies that appear to be needed – these are all included in IKVM’s /bin folder).

As a trivial example, here’s how you can use HtmlUnit with NUnit to load the Google homepage:

[TestFixture]
public class GoogleTests
{
    private WebClient webClient;
    [SetUp] public void Setup() { webClient = new WebClient(); }
 
    [Test]
    public void Can_Load_Google_Homepage()
    {
        var page = (HtmlPage)webClient.getPage("http://www.google.com");
        Assert.AreEqual("Google", page.getTitleText());
    }
}

Slightly more interesting, here’s how you can fill out a form (again, Google Search), click a button, and inspect the results:

[Test]
public void Google_Search_For_AspNetMvc_Yields_Link_To_Codeplex()
{
    var searchPage = (HtmlPage)webClient.getPage("http://www.google.com");
    ((HtmlInput)searchPage.getElementByName("q")).setValueAttribute("asp.net mvc");
    var resultsPage = (HtmlPage)searchPage.getElementByName("btnG").click();
 
    var linksToCodeplex = from tag in resultsPage.getElementsByTagName("a").toArray().Cast<htmlAnchor>()
                          let href = tag.getHrefAttribute()
                          where href.StartsWith("http://")
                          let uri = new Uri(href)
                          where uri.Host.ToLower().EndsWith("codeplex.com")
                          select uri;
    CollectionAssert.IsNotEmpty(linksToCodeplex);
}

As you can see, the API is full of Java idioms and feels a bit odd to a .NET developer. It would be great if someone decided to create a .NET wrapper library to expose a nicer API, .NET-style PascalCase, better use of IEnumerable and generics so LINQ queries were simpler, etc.

The other side of it is that HtmlUnit is very powerful. You can trivially scan the DOM with XPath, search for child elements, invoke events, much more easily than with WatiN.

To show that HtmlUnit has great JavaScript and Ajax support, here’s an example of automating a jQuery AutoComplete plugin to check its suggestions:

[Test]
public void jQuery_Autocomplete_Lon_Suggests_London()
{
    // Arrange: Load the demo page
    var autocompleteDemoPage = (HtmlPage)webClient.getPage("http://jquery.bassistance.de/autocomplete/demo/");
 
    // Act: Type "lon" into the input box
    autocompleteDemoPage.getElementById("suggest1").type("lon");
    webClient.waitForBackgroundJavaScript(1000);
 
    // Assert: Suggestions should include "London"
    var suggestions = autocompleteDemoPage.getByXPath("//div[@class='ac_results']/ul/li").toArray().Cast<htmlElement>().Select(x => x.asText());
    CollectionAssert.Contains(suggestions, "London");
}

In case you want to download all this code as a demo project you can run without needing IKVM or Java or anything weird, I’ve put it on GitHub.

Summary

This is experimental! I haven’t used this on any real project, though it was pretty effortless so far, so I’d definitely consider it. I don’t know how much faster HtmlUnit would be than a real browser, but it does bypass the trickiness of real browsers, which may be worth it alone. If I was going to use it seriously, I’d definitely make some .NET wrapper classes to hide the Java naming and idioms. HtmlUnit on .NET feels robust, but I haven’t pushed it hard.

READ NEXT

Deleporter: Cross-Process Code Injection for ASP.NET

Deleporter is a little .NET library that teleports arbitrary delegates into an ASP.NET application in some other process (e.g., hosted in IIS) and runs them there. At the moment, it’s still pretty experimental, but I’ve found it useful so far.

Published Mar 9, 2010