Attaching Watir to an Embedded IE Browser

Watir is a great tool for automating web tests. It controls IE through its COM interface. Unfortunately however, Watir has no easy method for attaching to an embedded IE window. Google searches on the topic have little to offer on the topic. One poster offers a solution which he/she thought would work but failed in the end.

I have yet to come up a working solution for attaching to the com interface of an embedded IE browser using ruby. However, you can attach to an embedded IE instance using autoit's UDF (User Defined Functions) library called IE.au3.

Here is an example of how it works:

#include <IE.au3>
#include <Array.au3>
 
; Window title of the application
$sAppTitle = "My Aplication Title"
 
$sAppPath = "C:\Program Files\My Company\Some.exe"
 
; Execute the application
ShellExecute($sAppPath)

$fResult = WinWait($sAppTitle, "", 20)
;app has loaded but it takes time for the embedded ie object to load
Sleep(5000)
; Attempt to attach to the embedded IE Object
$oIE = _IEAttach($sAppTitle, "Embedded")
; Verify the attempted attach was successful
If @error Then
	MsgBox(0, "Error", "Unable to attach to the embedded IE Object.")
	Exit
EndIf
 
; at this point you have your ole IE object.
;so you can call whatever __IE functions are available
;(check IE.au3 for complete reference)
 
; For example, get the tables: 
$oTables = _IETableGetCollection($oIE)
; Loop through each table, writing it to an array, then display the array
For $oTable In $oTables
	$aTable = _IETableWriteToArray($oTable, 1)
	_ArrayDisplay($aTable)
Next

Alright so this is fine and great, but I want to do this in ruby because I'm using it to test everything else. So if autoit can attach to an embedded IE window, why can't I do it in ruby. In reality there is no reason why you can't do this in Ruby. At this point its simply a matter of my lack of understanding of COM/OLE object in microsoft and how to use them. However, I have gained a bit of understanding about what must be accomplished in order to attach to an embedded IE window so I'll describe what I've learned, and hopefully with some collaboration, I can get this working.

My initial naive hope was that I could use the attach method of the Watir::IE.
Lets take a look at the documentation:


attach(how, what)

Return a Watir::IE object for an existing IE window. Window can be referenced by url, title, or window handle. Second argument can be either a string or a regular expression in the case of of :url or :title. IE.attach(:url, ‘www.google.com’) IE.attach(:title, ‘Google’) IE.attach(:hwnd, 528140) This method will not work when Watir/Ruby is run under a service (instead of a user).

Alright so if that is the case, all I need to get is the hwnd of the window with the embedded browser right? WRONG! This doesn't help at all. To understand why lets take a look at some of the watir code.
the attach method will call _attach_init method with the same parameters it recieved, then that method will call attach_browser_window with the same parameters. This method basically just calls the _find method with the same parameters. So finally the _find method is actually going to do something. It calls the each method of the IE class, passing in a block to do some pattern matching on the ie window that each iteration gives. The Each method is the culprit.
Lets take a look at the code:

 def self.each
      shell = WIN32OLE.new('Shell.Application')
      shell.Windows.each do |window|
        next unless (window.path =~ /Internet Explorer/ rescue false)
        next unless (hwnd = window.hwnd rescue false)
        ie = IE.bind(window)
        ie.hwnd = hwnd
        yield ie
      end
    end

the WIN32OLE.new('Shell.Application').Windows.each is going to loop through all open shell applications, and for each of them it will check of the path contains internet explorer. Thus, its never going to get an embedded browser window. When I attempted to print information about each window, my application (which was open) didnt show up in the list of windows at all. The interesting thing to note here is the call to IE.bind(window). This is the call that actually creates the ie object that all of the Watir based functionality will use. So what does the bind method do?

 def self.bind window
      ie = new true
      ie.ie = window
      ie.initialize_options
      ie
end

The ie.ie object (window) is what will be used by watir. So theoretically we should be able to just get the corresponding window object of our embedded browser and use that.
However, I've had a hell of a time trying to figure out how to get this. The best idea I could think of was looking up how the autoit IE.au3 guys did it (since that is all open source as well). So I took a look at the _IEAttach method that I showed in my autoit script mentioned earlier. The second parameter you can specify that the browser object is embedded by passing the string: "Embedded"
If this function is passed that string, it will first check for a window title match, and then it will get the hwnd from the embedded IE object (with a classname of Internet Explorer_Server). Now that they have the hwnd, they call the function _IEControlGetObjFromHWND($hwnd)
Now this is basically the holy grail function as far as I'm concerned. Here is the code:

;===============================================================
;
; Function Name:    __IEControlGetObjFromHWND()
; Description:		
; Returns a COM Object Window reference to an embebedded Webbrowser 
; control
; Parameter(s):	$hWin - HWND of a Internet Explorer_Server1 control 
; obtained for example:
;		$hwnd = ControlGetHandle("MyApp","","Internet Explorer_Server1")
; Requirement(s):   Windows XP, Windows 2003 or higher.
;	Windows 2000; Windows 98; Windows ME; Windows NT 
; may install the 	Microsoft Active Accessibility 2.0 Redistributable:
;===============================================================
;
Func __IEControlGetObjFromHWND(ByRef $hWin)
	DllCall("ole32.dll", "int", "CoInitialize", "ptr", 0)
	Local Const $WM_HTML_GETOBJECT = __IERegisterWindowMessage("WM_HTML_GETOBJECT")
	Local Const $SMTO_ABORTIFHUNG = 0x0002
	Local $lResult, $typUUID, $aRet, $oIE

	__IESendMessageTimeout($hWin, $WM_HTML_GETOBJECT, 0, 0, $SMTO_ABORTIFHUNG, 1000, $lResult)

	$typUUID = DllStructCreate("int;short;short;byte[8]")
	DllStructSetData($typUUID, 1, 0x626FC520)
	DllStructSetData($typUUID, 2, 0xA41E)
	DllStructSetData($typUUID, 3, 0x11CF)
	DllStructSetData($typUUID, 4, 0xA7, 1)
	DllStructSetData($typUUID, 4, 0x31, 2)
	DllStructSetData($typUUID, 4, 0x0, 3)
	DllStructSetData($typUUID, 4, 0xA0, 4)
	DllStructSetData($typUUID, 4, 0xC9, 5)
	DllStructSetData($typUUID, 4, 0x8, 6)
	DllStructSetData($typUUID, 4, 0x26, 7)
	DllStructSetData($typUUID, 4, 0x37, 8)

	$aRet = DllCall("oleacc.dll", "long", "ObjectFromLresult", "lresult", $lResult,  "ptr", DllStructGetPtr($typUUID),  "wparam", 0, "idispatch*", 0)

	If IsObj($aRet[4]) Then
		$oIE = $aRet[4] .Script()
		; $oIE is now a valid IDispatch object
		Return $oIE.Document.parentwindow
	Else
		SetError(1)
		Return 0
	EndIf
EndFunc   ;

This is a bit confusing. Certainly not impossible to implement in ruby but I'm not sure about a few parts. I will probably attemp to do so tomorrow so long as I don't find a better solution. This is basically as far as my research has gone. I've tried a ton of other things that didn't amount to anything. I can get the hwnd already so I do not need to implement the attach method, just this one.
If anyone has any ideas I'm all ears.

I should be back soon to report my findings.

figure it out

Ever get this one, or have any other clues to my question (how to attach an activeX componenet to an hwnd in ruby)?
Thanks!
-r

No I didn't, however there

No I didn't, however there was some talk by a win32api developer that this should be possible with the new release he was working on at the time.. Not sure what ever happened with that.. However I've since moved on from qa and work as a regular developer primarily.

Another source that may help though:

http://rubyforge.org/pipermail/wtr-development/2009-August/001175.html

interesting

Thanks. Also saw this post,which might be related (for followers)

http://rubyforge.org/forum/forum.php?set=custom&forum_id=319&style=neste...

Also do you remember where you got the autoit source code from? I don't seem to see it easily available anywhere...

Original Source

I think I had to download the source for autoit to get this.. It may have been some extension pack though.. Google for __IEControlGetObjFromHWND and see if you get anything (too busy to help right now).