views:

516

answers:

1

Some Background on architecture of the app is needed:

Windows 2003/Apache-v2.2/IE7/Watir-v1.6.2/Ruby-v1.8.5

  1. Apache running under 'localsystem' account.
  2. Request to run a Watir script comes in.
  3. Apache CGI kicks off IE7 under a particular user, e.g. 'tester', and attaches the IE7 window to the "default" desktop environment. This allows us to VNC into the machine and see all IE7 windows from all the accounts running.
  4. IE7 has ruby embedded into the process through plugin and executes the ruby script in a thread.

For clicking on links/buttons in IE, Watir supplies a synchronous 'click' method and async 'click_no_wait' method. 'click_no_wait' spawns a completely new ruby process to connect back to the IE7 window to click the link/button.

This is what is failing for me. Since click_no_wait is spawning a new process. It cannot seem to see the IE7 window to connect to it and click on the link/button. I have to use 'click_no_wait' due to a dialog box that IE pops up on a certain page, so that another ruby thread can close it.

I've tried a few things: - using fire_event('OnClick') instead of click_no_wait hangs script like 'click' would. - Modifying 'click' by commenting out '@container.wait', but the 'click!' itself is the API that hangs waiting for the dialog box to close. - Make 'click_no_wait' spawn a new thread instead of a process', but other threads appear to be suspended while the 'click!' call executes in that thread. Which is strange. - Executing the 'click_no_wait' process using the exact same code that is used to spawn the IE7 process, but it still can't find any IE7 windows.

By 'default' desktop described above, the code that spawns IE basically does a series of C++ calls to attach the IE7 window to the 'winsta0' desktop:

- LogonUser() // log in as tester account
- OpenWindowStation("winsta0") // to get default desktop
- SetProcessWindowStation()
- // add the user to interactive window station using (GetUserObjectSecurity, GetSecurityDescriptorDacl, GetAclInformation, AddAce, SetSecurityDescriptorDacl, SetUserObjectSecurity)
- // add user to "default" desktop using APIs listed above.
- CreateEnvironmentBlock
- ImpersonateLoggedOnUser
- CreateProcessAsUser('iexplore.exe')   
- // cleanup

Running the following Watir script under the particular user IE7 is running under yields:

$IE = Watir::IE.attach(:title, /Google/)
$IE.button(:name, 'btnG').click!

c:/ruby/lib/ruby/gems/1.8/gems/watir-1.6.2/lib/watir/ie-class.rb:246:in `method_missing': Windows (WIN32OLERuntimeError) OLE error code:80040154 in HRESULT error code:0x80020009 Exception occurred. from c:/ruby/lib/ruby/gems/1.8/gems/watir-1.6.2/lib/watir/ie-class.rb:246:in "each"

Which contains:

c:/ruby/lib/ruby/gems/1.8/gems/watir-1.6.2/lib/watir/ie-class.rb:246
shell = WIN32OLE.new("Shell.Application")
windows = shell.Windows   ## Fails here

Let me know if I can supply more details or disambiguation! :)

Thanks, Shane.

A: 

Ok, I have solved my own issue.

It turns out that the method that Watir uses to enumerate IE windows (i.e. Shell.Application) does not work across user spaces. So I had to write an intermediate application to retrieve the IWebBrowser2 COM pointer for the IE window, and modify the 'eval_in_spawned_process' Watir function to use this handle for 'pc = Watir::IE.bind(iWebBrowserPtr)'.

It's possible that I didn't need the external app to retrieve the IWebBrowser2 object, but I didn't know how to do it in Ruby, and I already had an IE BHO app that I could leverage for the purposes.

This now works fine. :)

Watir/page-container.rb:56

# This evaluates a cmd (e.g. "button(:id, 1).click!") in a new ruby process
# This is to primarily avoid a script hanging while a modal dialog is displayed in IE.
# It gets an IE handle from our IE BHO based off of window handle.  It needs to do this 
# to get around the problem of finding the IE window itself using Shell.Application
# which does not work across User-contexts.
# Note: This change probably invalidates the Watir unit-test for this API.
def eval_in_spawned_process(command)
  command.strip!
  load_path_code = _code_that_copies_readonly_array($LOAD_PATH, '$LOAD_PATH')
  ruby_code = "require 'watir/ie'; "
  ruby_code << 'controller = WIN32OLE.new("HttpPlugin.Controller"); '
  ruby_code << "window = controller.BrowserFromHWND(#{hwnd}); "
  ruby_code << "pc = Watir::IE.bind(window); "
  ruby_code << "pc.#{command}; "
  exec_string = "start rubyw -e #{(load_path_code + '; ' + ruby_code).inspect}"
  result = system(exec_string)
end
Shane