Auto Testing Browser Control Applications

Build Environment: VC 6.0
Runtime Environment: Any Windows computer with IE 4.0 or later

Introduction

When an application is hosting the browser control, and significant parts of the user interaction are happening in HTML inside the browser control, it can be difficult to write test scripts for that application. For instance, test tools such as SilkTest cannot determine where the controls or user interface elements are located inside the HTML. This article will describe a way to programmatically test such applications, without adding code to the application itself.

You can use the techniques and tools included here to interact with Internet Explorer, but there are also other ways to do that.

Technique

The technique we use is based on the Accessibility DLL, OLEACC.dll. A Technical Overview is available from MSDN. The classes provided hide the interaction with OLEACC.dll. Any program using OLEACC.dll directly should check for its presence before calling into it.

The general approach with the accessibility APIs used here gives an interface pointer to an object through a SendMessage() call. In the case of the browser control, the interface we get is IHTMLDocument2. (This is assuming IE 5.0 or 5.5. The approach will work with IE 4.0 and later, but the version of IHTMLDocument that you get back may be different.) The SendMessage() call must go directly to the browser control window, not any of the wrapper Shell or ATL windows, such as “Shell Embedding” or “Shell DocObject View.” Fortunately, EnumChildWindows recurses through nested child windows, so it isn’t too difficult to find the control window proper.

The steps required to get the IHTMLDocument interface are as follows:

  1. Load the OLEACC.dll and find the GetObjectFromLresult function.
  2. Find the control window, classname of “Internet Explorer_Server.”
  3. Send the window the WM_HTML_GETOBJECT message. This is a registered Windows message.
  4. Pass the result of SendMessage to GetObjectFromLresult.

Assuming these calls succeed, you now have a pointer to an IHTMLDocument2 interface.

If you start using this IHTMLDocument2 interface to access the document displayed in the browser control, you may not get the results you expect. This is because the interface does not represent the documents that are currently loaded. It represents the virtual frame containing your document. This is true even if the HTML document does not use frames. It has something to do with the internal data structures of IE. To get from the document interface that was retrieved from GetObjectFromLresult, to a document interface that represents the loaded HTML document, follow these steps:

  if (spDocIntf)
  {
    CComPtr<IDispatch> spDisp;
    CComQIPtr<IHTMLWindow2> spWin;
    CComPtr<IHTMLDocument2> spDoc;

    if (FAILED(spDocIntf->get_Script(&spDisp)))
      return E_FAIL;
    spWin = spDisp;    //Get IHTMLWindow2 thru QueryInterface
    if (spWin)
      spWin->get_document(&spDoc);
    //Now do your thing with spDoc...
  }

If you don’t use ATL and the nifty CComQIPtr class, you must manually call QueryInterface() on spDisp to get the IHTMLWindow2 interface.

Accessing Elements

Once you have a pointer to the IHTMLDocument2 interface, accessing individual elements inside your HTML document is fairly straightforward, by using the Document Object Model. There is no semantical difference between accessing the DOM through scripting, through COM, or even when writing the HTML in the first place. To find an element, we use the all property of the IHTMLDocument2 object, then look for a named element using the item method of IHTMLElementCollection.

About HTMLDrv

The HTMLDrv project wraps the functionality described above, and more, in a couple of convenient classes. It builds as a DLL with exported functions. This makes it easy to use from SilkTest and other testing tools.

Our goal was to test our browser control hosting application using automated SilkTest scripts. There were several options we could have pursued. We chose to have HTMLDrv issue commands directly to the HTML elements inside our documents. Because we were going to test the navigational and purely functional aspects of our application, this was a good and simple approach. If you are testing sophisticated user interactions, such as significantly reacting to events like OnMouseOver and OnKeyDown, you may want to have your test tool perform the input simulations directly on the interface elements. Using the IHTMLDocument2 interface can still be useful because you can get information about where the elements are on the screen (not to mention what elements are present). In that scenario, you will have to do a little more legwork not described in this article.

HTMLDrv contains two classes. WebCtlHandler is the main class. It attaches to a top-level window and dispatches commands to the browser control inside that window. Each WebCtlHandler instance can only be attached to one particular window. The object is attached through a GetDocInterface() call. This method takes a handle to a window; this should be the top-level window. GetDocInterface() performs the steps described above under Technique. Because we wanted HTMLDrv to be compatible with SilkTest, we had to call CoInitialize/CoUninitialize from within the DLL. For this reason, WebCtlHandler ensures that all calls are on the same thread. Otherwise, the COM calls would fail. WebCtlHandler has a couple of methods to invoke commands, one basic InvokeCommand() method, and a second method that is necessary if the HTML document uses frames. In the latter case, WebCtlHandler must know in which frame to dispatch the command.

CHTMLCommand is a class that represents a command. It is responsible for finding a particular control and interacting with it depending on the desired directive. A control, or HTML element, is identified through its name or ID. There are a dozen or so possible directives. The most obvious one is “Click,” which will initiate the OnClick event on the HTML element. The rest involves getting or setting the text, getting or setting the value (specific to input elements), setting or clearing check marks, and so forth.

The third source file in HTMLDrv, HTMLDrv.cpp, contains the entry points for the dll, including DllMain(). All the entry points, along with directives and error codes, are documented in ReadMe.txt.

Sample Files

Included in the samples is a DllTest project. This serves two purposes: It is a sample of how to use HTMLDrv from a Windows application, and it also serves as a testing and debugging tool. test.html is a sample test file to play with. To get up and running, perform these simple steps:

  1. Download the projects.
  2. Build HtmlDrv and DllTest (binaries should go in the same directory; the DllTest project file is set up so that the executable ends up in the same directory as HTMLDrv.dll, assuming DllTest is a subdirectory to HTMLDrv).
  3. Double-click test.html.
  4. Run DllTest. Enter “IEFrame” under “Control-id,” and click “Get Wnd.”
  5. Enter “IdTestBtn” under “Control-id” and click the “Click” button.
  6. Click the “Value” button.

Your window should now look like the picture above.

The file excerpt.inc shows a way to use the HTMLDrv dll from a SilkTest script.

Summary

By using the techniques described in this article, you can get information about, and programmatically interact with, user interfaces displayed in the IE Browser Control. Since these techniques does not require a modification of the application itself, you can even use them to interact with third-party software that uses the browser control. You can use the HTMLDrv dll as is, add to it or modify it, or you can extract the WebCtlHandler and CHPHTMLCommand classes; the latter in particular is generic enough to be used in other contexts.

Downloads


Download HTMLDrv source – 11 Kb


Download DllTest source – 8 Kb


Download binary files (includes ReadMe.txt) – 37 Kb

More by Author

Previous article
Next article

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read