Debugging and Improving Watir’s click_no_wait Method

Written by: Jarmo Pertman on December 28, 2010

Originally posted at itreallymatters.net by Jarmo Pertman.


I’ve written about debugging Watir’s click_no_wait method problems before. This time i’m gonna do it again because starting from Watir 1.6.6 the #click_no_wait method itself has changed along with the way to debug it’s problems.

In this post i’m gonna also write more about the inner-workings of #click_no_wait and the changes made to it in Watir 1.6.6.

There was mainly 3 motivations to change #click_no_wait:

  • Make it faster!
  • Make it easier to debug!
  • Make the code itself cleaner and better!

Making it Faster

Before going into the dirty details how it became faster i’m gonna give a short overview how it was working before the changes. The main parts of the #click_no_wait were the following methods:

# watir/element.rb
def click_no_wait
  assert_enabled

  highlight(:set)
  object = "#{self.class}.new(self, :unique_number, #{self.unique_number})"
  @page_container.eval_in_spawned_process(object + ".click!")
  highlight(:clear)
end

# watir/page-container.rb
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 << "pc = #{attach_command}; " # pc = page container
  ruby_code << "pc.instance_eval(#{command.inspect})"
  exec_string = "start rubyw -e #{(load_path_code + '; ' + ruby_code).inspect}"
  system(exec_string)
end

# watir/ie.rb
def _code_that_copies_readonly_array(array, name)
  "temp = Array.new(#{array.inspect}); #{name}.clear; temp.each {|element| #{name} << element}"
end

gist.github.com/634798#file_click_no_wait.rb

The main entrypoint is of course the #click_no_wait method itself which executes #eval_in_spawned_process in page-container. That in turn runs start rubyw with a system execution in a separate Ruby process where all the dirty work is done. The job consists of the following components:

  • Loading Watir;
  • Attaching to the browser window;
  • Locating the element with an unique identifier and performing clicking on it.

The most time consuming part is not directly visible from the code above, but it was a require statement for watir/ie. This require statement triggered loading of everything in Watir. Even the things which are never needed in the “limited sandbox” of #click_no_wait. If you think of it then only core of Watir’s features are needed there to attach to the browser, locate the element and perform click on it. Every other advanced feature is not possible to use by #click_no_wait! Attaching to the browser, locating the element and performing click on it is pretty fast.

I inspected watir/ie.rb file and started to pick out all the require statements which might be needed in that sandbox. I ended up creating a separate file called watir/core.rb where only necessary files would be loaded. The file itself is loaded also by watir/ie.rb so everything would be available if using Watir normally. In other words, core.rb is a subset of files needed by Watir. This change itself made #click_no_wait to perform 2-3 times faster on my machine! That was enough for the time.

Making it Easier to Debug

As also written in the previous post then the fact that everything is done in a separate Ruby process is giving the feature of not blocking, but taking away the advantage of seeing any errors on the console window as you’d normally expect. This means that usually nothing is clicked and nothing is shown on the console in case of some problem. Using debugger would not help much either since the problem itself was happening on a spawned Ruby process with no visible output on the screen (yes, you could have set a breakpoint to Element#click!, but that wouldn’t have helped much if the error occurred before getting to that point). Also, for trying to change anything to happen differently in that spawned process with monkey-patching would have involved of overriding the whole contents of #eval_in_spawned_process_method. Not a nice way to solve problems, i’d say.

I wanted to achieve some possibility to debug these problems easily without any need of using debugger or monkey-patches. I wanted to use regular Ruby’s $DEBUG variable. To achieve that i extracted the command, which is given to the system method into separate method which would return a little bit different command depending of the value of $DEBUG:

def click_no_wait
  # ...
  system(spawned_click_no_wait_command(ruby_code))
end

def spawned_click_no_wait_command(command)
  command = "-e #{command.inspect}"
  unless $DEBUG
    "start rubyw #{command}"
  else
    puts "#click_no_wait command:"
    command = "ruby #{command}"
    puts command
    command
  end
end

gist.github.com/634798#file_click_no_wait_debug.rb

If $DEBUG is set to false (which is the default case) then #click_no_wait works as usual, but if it’s set to true then it will output the executed command itself and will wait for the spawned Ruby process to exit so it’s possible to see all the error messages if any.

This approach also gives the opportunity to monkey-patch the executed command, which in turn allows to write some tests for the method itself.

To make it plain and clear then this is the way to turn on debugging for #click_no_wait:

$DEBUG = true
browser.button(:id => "something").click_no_wait

gist.github.com/634798#file_click_no_wait_debug_on.rb

Making the Code Itself Cleaner and Better

The original code had some commented out code and over-generalisation. There were instance_eval’s and all other neat tricks which were not needed at all. The most cumbersome was the method called _code_that_copies_readonly_array defined in the global scope with a slightly funny comment - “why won’t this work when placed in the module (where it properly belongs)” - i even tried to move that method into the module where it actually worked. A valid case of comments getting out of sync? The change allowed to delete that method entirely and not use instance_eval and friends to make whole code more understandable. The resulting code is like this:

def click_no_wait
  assert_exists
  assert_enabled
  highlight(:set)
  element = "#{self.class}.new(#{@page_container.attach_command}, :unique_number, #{self.unique_number})"
  ruby_code = "require 'rubygems';" <<
          "require '#{File.expand_path(File.dirname(__FILE__))}/core';" <<
          "#{element}.click!"
  system(spawned_click_no_wait_command(ruby_code))
  highlight(:clear)
end

gist.github.com/634798#file_click_no_wait_refactored.rb

Of course in the end it is all just a matter of taste. There is only one small problem with this code - usage of Rubygems. It is in my pipeline to remove it’s usage without making code look much uglier (there’s no way i’m gonna add back that _code_that_copies… method!) and making #click_no_wait even faster.

Taking into the consideration the fact that these changes involved changing core code then i’m quite happy that they got released into the wild with only one (known) bug!

Tags: