Monday 29 July 2013

Driving an Embedded WPF browser with Selenium (2) Web Driver

Recently our project team was tasked with something a bit strange.  A little history first.

The application we work on is a WPF application for performing simple business functions.  It "integrates" with a large external vendor system that has a user interface of it's own done over the web for the most part; and not enough of the API is exposed to rebuild our own user interfaces.  When doing a cost analysis it was deemed much cheaper to build fragile DOM parsed linkages and include the HTML user interface into our application then to have the vendor build the appropriate APIs (face palms following are welcomed, not a great story, but it's the world I'm stuck with and helps us arrive at our problem).

What is that problem?  How do you automate a test when a huge chunk of the application's functionality is build into a browser window?  Without these tests how do we determine when those fragile DOM linkages are broken (likely because the vendor changed the DOM structure or content)... before the current team of developers took over the project it was a manual process for the testers of checking each function of the system by hand that was possible given a release timeline.  In other words; we simply never caught everything before the code was pushed into production.  Subsequently every release would include 2 or 3 emergency patches mostly surrounding DOM integration and the embedded web component.

A little more history?  The previous development team had automated the pieces of the application that didn't involve the embedded browser using the White framework.  I was relatively happy with White and saw no reason to get rid of it; albeit since that framework had been created until today they were a little out of date obviously.  So a better title for this article may have even been "How to drive a WPF application using White and Selenium" or some such thing!

One more short digression...  I sent a few messages off to the Selenium team looking to see if there was interest in having an official patch and support for this.  I never got a response, so I'm not sure if the right people never saw it or if they aren't interested, but I refuse to spend time creating a well designed and supported extension only to have it rejected.  This is why a blog entry and not an official patch!

The first thing you'll need to know before we go any further; you will need to be comfortable compiling C++ (Selenium); mainly the Internet Explorer Server Driver component.  You'll also need to know a little about how Windows Handles work in Win32 programming and be comfortable with working around them.

Selenium is broken in half (for the purposes of this discussion): The .NET binary that is the API and satellite executable programs which actually integrate with and drive the browser.  For the hack we are about to orchestrate we really only needed to change the satellite Internet Explorer driver since we can hardcode how we locate the window.

Before: Driver creates a new instance of Internet Explorer and latched onto the process.  It proceeds to locate a windows handle within the process named "Internet Explorer_Server".  Once located some magic occurs that is irrelevant to what we care about and the application has control of that instance.

The goal here was to replace the IEDriverServer.exe with a version that no longer creates a new instance of Internet Explorer.  Instead we want to locate that windows handle within the WPF application and allow the magic to continue.  When we are done we also don't want to kill a process that never was created so there is some shutdown code to modify (or we would be stuck with a console window on our automation computer).  We will use the White framework to shutdown the application we are driving so really we need absolutely no shutdown code from Selenium at all.

So here is what changed (ignoring the solution and project file, they changed only because I was lazy and ignored a few things to compile):


1) Don't launch a new version of IE (IECommandExecutor.cpp)

Function: IECommandExecutor::CreateNewBrowser()

Remove the code using the factory to spawn a new browser process.

I also hard coded the dwProcessId to NULL just to be safe.

2) Don't search for the Windows Handle by process anymore (BrowserFactory.cpp)

Function: BrowserFactory::AttachToBrowser()

Obviously we don't have a process ID anymore.  Since this is a quick hack rather then a real solution lets just make this search out our WPF window by caption then sub-window search recursively until we locate the appropriate HWND for Internet Explorer.  The sample code here was what I used to located the default WPF MainWindow with a <WebBrowser> filling the window (adjustments are needed for your own implementation assuming you don't want to have this information or a more robust solution occurring at an API level in the C# side of the world).


**Note: The implementations for finding a window are fairly standard using EnumWindows and EnumChildWindows.  It feels pedantic to include it here.
I looked around briefly on request in comments; I don't have the implementation for the window searching above.  You can kind of get an idea for how the searching was implemented from the definition above, but you will need Google if you aren't familiar with searching for windows in the WinApi.

3) Shutdown (IESession.cpp & Browser.cpp)... Optional if you don't require Quit() at the API

Function: Browser::Close()
Function: IESession::Shutdown()

When you try to .Quit() at the API if you haven't followed these directions two things will happen:
  1. The console window will hang then eventually time-out and crash
  2. The WPF application will act very strange as the browser attempts to actually close out from whatever drives it
Basically we just need to remove all the shutdown code as follows.


**Note: I kept the section about event firing being shut off.  It actually appears to do something useful even in this context!

Profit?

The last thing you need to do is go set it all up.  We used NuGet to install Selenium WebDriver into our testing projects which already were setup with White.  There were a series of tests that already drove our application with White to eventually bring up a screen that had a web browser embedded in them.  When that screen needed to be interacted with an InternetExplorerDriver() was instanced which will spawn IEDriverServer.exe (which we patched with our hacked version) and that will attach to the WPF instance and voila... follow standard Selenium design and syntax to automate your WPF browser.

11 comments:

  1. Have worked? can you post some example code?

    Thanks in advance.

    ReplyDelete
  2. Pedantic to you...but what about us folk who haven't done C++ for many years and have to struggle to power up those brain cells. LOL...but its true...anyway it would have been nice if you included that pedantic code too.

    ReplyDelete
    Replies
    1. Sorry this was a few years ago; I don't think I have the EnumWindows code lying around anymore. It should be easy enough to google; something like "find window control by class name" (or title instead of class name, depending on what your looking for). EnumChildWindow would be useful for digging down for a specific control.

      Delete
  3. Hello.

    I have implemented your solution and it is working ok (great job), but the test cases over the embedded application are executed very slow. Do you know some fix for this problem?

    ReplyDelete
    Replies
    1. I forgot to add more information: I built the selenium code for Win32. Our application can be executed in Web or embedded into a desktop application. The test cases executed over the IE are working normally, but the same test case over the desktop application (same environment), is very slow typing characters into the text fields.

      Delete
    2. Hi.

      I'm not sure how much this will help; bearing in mind I haven't played with this source code in a few years now.

      I vaguely remember a debugging mechanism built into the system; when you build the code in Debug the text input is incredibly slow. When you compiled release the event stream would be at normal pace. Since your description hints that the same code (Web Driver) is powering both IE and the WPF browser at the same time, I suspect it isn't that simple. But it is possible that same debugging mechanism is somehow getting triggered for you.

      I vaguely remember a colleague suffering the same problem and I had to fix it for him. I can't recall right now how we fixed it though.

      Sorry I can't be more help.

      Delete
    3. Which version of .net are you running this semi-tutorial post against? I'm quite happy knowing this still works 2 years later!

      Delete
    4. Finally I found a solution. I built the IEDriver and the Selenium for x64, and it works ok. I am using the selenium 2.50 release code and the Visual Study 15 with .NEt 4.6.

      Delete
    5. @jmsc : i'm too try t automate desktop application(.exe) which displays embedded browser, can u please guide me on this on how to use selenium along with C# to automate the application

      Delete
  4. Hello ,

    Can You post the updated source or a ready executable file

    ReplyDelete
    Replies
    1. Hi. No sorry I don't have any of this anymore so I can't share any of what you are asking for. This article was written to help someone do it themselves; I never intended to distribute it as a working solution (actually it was a proof of concept to show it was possible). At one point I considered adding this as a patch request to the project maintainers but I never got a response to my request back then; it's far too late for me to do this now unfortunately. You will need to check out the C++ and build your own version sorry.

      Delete