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!