[Wtr-development] [JIRA] Updated: (WTR-449) making Element#click_no_wait faster and easier debuggable

Jarmo Pertman (JIRA) watirjira at gmail.com
Wed Jul 28 17:55:30 EDT 2010


     [ http://jira.openqa.org/browse/WTR-449?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel ]

Jarmo Pertman updated WTR-449:
------------------------------

    Description: 
I have been thinking of improving Element#click_no_wait for some time now. I can see the following problems with current solution:

1) code is not good - method _code_that_copies_readonly_array with funny comment in main namespace, eval_in_spawned_process with TODO and not-TODO comments

2) users have a lot of problems with click_no_wait (http://bit.ly/asirep) and current code doesn't allow easy debugging with monkey-patching or similar solutions

3) in the spawned process used for click_no_wait, no rubygems is loaded thus it's possible that users without having RUBYOPT set to -rubygems can't use click_no_wait and since it's hard to debug (see point #2) then they don't even know what's the problem

4) click_no_wait is just slow because it loads too many files/libraries in it's own process - it should just load all the necessary things and nothing else

5) currently, in the unit tests click_no_wait process doesn't use development libraries

Here is a benchmark:

*click_no_wait_bench.rb:*
{noformat}
require "rubygems"
require "watir"
require "benchmark"

b = Watir::Browser.new
b.goto "http://www.google.com"
times = 5

# almost original watir 1.6.5 code
# * added gsub to avoid bug for some ruby versions
# * using ruby instead of start rubyw to measure time

module Watir
  module PageContainer
    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 = "$HIDE_IE = #{$HIDE_IE};" # This prevents attaching to a window from setting it visible. However modal dialogs cannot be attached to when not visible.
      ruby_code << "pc = #{attach_command}; " # pc = page container
      # IDEA: consider changing this to not use instance_eval (it makes the code hard to understand)
      ruby_code << "pc.instance_eval(#{command.inspect})"
      exec_string = "ruby -e #{(load_path_code + '; ' + ruby_code).inspect}".gsub("\\\"", "'")
      system(exec_string)
    end
  end
end

Benchmark.bmbm do |x|
  x.report("watir 1.6.5") {times.times {b.button(:name => "btnG").click_no_wait}}
end

=begin
Rehearsal -----------------------------------------------
watir 1.6.5   0.031000   0.015000   0.046000 ( 26.136495)
-------------------------------------- total: 0.046000sec

                  user     system      total        real
watir 1.6.5   0.000000   0.000000   0.000000 ( 23.600350)
=end


# improved section
# * simplified click_no_wait methods
# * requiring rubygems incase of missing RUBYOPT
# * extracted ruby_execute_command into method for easier monkey-patching for debugging and testing reasons
module Watir
  class Element
    def click_no_wait
      assert_enabled

      highlight(:set)
      element = "#{self.class}.new(#{@page_container.attach_command}, :unique_number, #{self.unique_number})"
      @page_container.click_no_wait(element)
      highlight(:clear)
    end
  end

  module PageContainer
    def click_no_wait(element)
      ruby_code = "require 'rubygems';" <<
              "require 'watir/ie';" <<
              "#{element}.click!"
      system(spawned_click_no_wait_command(ruby_code))
    end

    def spawned_click_no_wait_command(command)
      "start rubyw -e #{command.inspect}"
    end

    private :spawned_click_no_wait_command
  end
end

Benchmark.bmbm do |x|
  x.report("watir improved - ie") {times.times {b.button(:name => "btnG").click_no_wait}}
end

=begin
Rehearsal -------------------------------------------------------
watir improved - ie   0.032000   0.047000   0.079000 ( 20.141152)
---------------------------------------------- total: 0.079000sec

                          user     system      total        real
watir improved - ie   0.047000   0.016000   0.063000 ( 18.931083)
=end

# * loading watir/core.rb to reduce loading of unneeded files/libraries
module Watir
  class Element
    def click_no_wait
      assert_enabled

      highlight(:set)
      element = "#{self.class}.new(#{@page_container.attach_command}, :unique_number, #{self.unique_number})"
      @page_container.click_no_wait(element)
      highlight(:clear)
    end
  end

  module PageContainer
    def click_no_wait(element)
      ruby_code = "require 'rubygems';" <<
              "require 'watir/core';" <<
              "#{element}.click!"
      system(spawned_click_no_wait_command(ruby_code))
    end

    def spawned_click_no_wait_command(command)
      "start rubyw -e #{command.inspect}"
    end

    private :spawned_click_no_wait_command
  end
end

Benchmark.bmbm do |x|
  x.report("watir improved - click_no_wait") {times.times {b.button(:name => "btnG").click_no_wait}}
end

=begin
Rehearsal ------------------------------------------------------------------
watir improved - click_no_wait   0.062000   0.015000   0.077000 (  9.729557)
--------------------------------------------------------- total: 0.077000sec

                                     user     system      total        real
watir improved - click_no_wait   0.015000   0.047000   0.062000 (  9.308532)
=end

b.close
{noformat}

*watir/core.rb:*
{noformat}
require 'watir/win32ole'

require 'logger'
require 'watir/exceptions'
require 'watir/matches'

require 'watir/core_ext'
require 'watir/logger'
require 'watir/container'
require 'watir/locator'
require 'watir/page-container'
require 'watir/ie-class'
require 'watir/element'
require 'watir/element_collections'
require 'watir/form'
require 'watir/non_control_elements'
require 'watir/input_elements'
require 'watir/table'
require 'watir/image'
require 'watir/link'
require 'watir/html_element'

require 'watir/waiter'
require 'watir/module'
{noformat}

As it's possible to see from the results then i got click_no_wait to perform faster about 2-3 times (of course this depends on the PC and so on, but these are the results from my machine, which is a quite modest laptop)!

I call this as a quite big improvement if we think about all the Web 2.0 websites where #click_no_wait is often used.

Results:

1) removed all this unnecessary and funny-looking code which also had slight of a "criminal overengineering" smell on it (http://coderoom.wordpress.com/2010/06/23/criminal-overengineering/)

2) it is now easy debuggable, just use this monkey-patch:
{noformat}
Watir::PageContainer.class_eval {def spawned_click_no_wait_command(command) "ruby -e #{command.inspect}" end}
{noformat}

Maybe make it even more easier by using $DEBUG and if-statement or something similar?

3) requiring rubygems always, just in case, in the spawned process

4) click_no_wait performs 2-3x faster! It might be possible to improve the performance even more by doing some larger changes with the require statements in watir/core.rb - unfortunately it is not possible anymore to make changes in smaller steps

5) all click_no_wait invocations in unit tests use now development libraries

Fixes are within commits:
http://github.com/jarmo/watir/commit/7333081e30568ec73eb63f9f75562c2078c55481
http://github.com/jarmo/watir/commit/c71cee1ac820493372d299b8f3a258b497b22e66

  was:
I have been thinking of improving Element#click_no_wait for some time now. I can see the following problems with current solution:

1) code is not good - method _code_that_copies_readonly_array with funny comment in main namespace, eval_in_spawned_process with TODO and not-TODO comments

2) users have a lot of problems with click_no_wait (http://bit.ly/asirep) and current code doesn't allow easy debugging with monkey-patching or similar solutions

3) in the spawned process used for click_no_wait, no rubygems is loaded thus it's possible that users without having RUBYOPT set to -rubygems can't use click_no_wait and since it's hard to debug (see point #2) then they don't even know what's the problem

4) click_no_wait is just slow because it loads too many files/libraries in it's own process - it should just load all the necessary things and nothing else

5) currently, in the unit tests click_no_wait process doesn't use development libraries

Here is a benchmark:

*click_no_wait_bench.rb:*
{noformat}
require "rubygems"
require "watir"
require "benchmark"

b = Watir::Browser.new
b.goto "http://www.google.com"
times = 5

# almost original watir 1.6.5 code
# * added gsub to avoid bug for some ruby versions
# * using ruby instead of start rubyw to measure time

module Watir
  module PageContainer
    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 = "$HIDE_IE = #{$HIDE_IE};" # This prevents attaching to a window from setting it visible. However modal dialogs cannot be attached to when not visible.
      ruby_code << "pc = #{attach_command}; " # pc = page container
      # IDEA: consider changing this to not use instance_eval (it makes the code hard to understand)
      ruby_code << "pc.instance_eval(#{command.inspect})"
      exec_string = "ruby -e #{(load_path_code + '; ' + ruby_code).inspect}".gsub("\\\"", "'")
      system(exec_string)
    end
  end
end

Benchmark.bmbm do |x|
  x.report("watir 1.6.5") {times.times {b.button(:name => "btnG").click_no_wait}}
end

=begin
Rehearsal -----------------------------------------------
watir 1.6.5   0.031000   0.015000   0.046000 ( 26.136495)
-------------------------------------- total: 0.046000sec

                  user     system      total        real
watir 1.6.5   0.000000   0.000000   0.000000 ( 23.600350)
=end


# improved section
# * simplified click_no_wait methods
# * requiring rubygems incase of missing RUBYOPT
# * extracted ruby_execute_command into method for easier monkey-patching for debugging and testing reasons
module Watir
  class Element
    def click_no_wait
      assert_enabled

      highlight(:set)
      element = "#{self.class}.new(#{@page_container.attach_command}, :unique_number, #{self.unique_number})"
      @page_container.click_no_wait(element)
      highlight(:clear)
    end
  end

  module PageContainer
    def click_no_wait(element)
      ruby_code = "require 'rubygems';" <<
              "require 'watir/ie';" <<
              "#{element}.click!"
      system(spawned_click_no_wait_command(ruby_code))
    end

    def spawned_click_no_wait_command(command)
      "start rubyw -e #{command.inspect}"
    end

    private :spawned_click_no_wait_command
  end
end

Benchmark.bmbm do |x|
  x.report("watir improved - ie") {times.times {b.button(:name => "btnG").click_no_wait}}
end

=begin
Rehearsal -------------------------------------------------------
watir improved - ie   0.032000   0.047000   0.079000 ( 20.141152)
---------------------------------------------- total: 0.079000sec

                          user     system      total        real
watir improved - ie   0.047000   0.016000   0.063000 ( 18.931083)
=end

# * loading watir/core.rb to reduce loading of unneeded files/libraries
module Watir
  class Element
    def click_no_wait
      assert_enabled

      highlight(:set)
      element = "#{self.class}.new(#{@page_container.attach_command}, :unique_number, #{self.unique_number})"
      @page_container.click_no_wait(element)
      highlight(:clear)
    end
  end

  module PageContainer
    def click_no_wait(element)
      ruby_code = "require 'rubygems';" <<
              "require 'watir/core';" <<
              "#{element}.click!"
      system(spawned_click_no_wait_command(ruby_code))
    end

    def spawned_click_no_wait_command(command)
      "start rubyw -e #{command.inspect}"
    end

    private :spawned_click_no_wait_command
  end
end

Benchmark.bmbm do |x|
  x.report("watir improved - click_no_wait") {times.times {b.button(:name => "btnG").click_no_wait}}
end

=begin
Rehearsal ------------------------------------------------------------------
watir improved - click_no_wait   0.062000   0.015000   0.077000 (  9.729557)
--------------------------------------------------------- total: 0.077000sec

                                     user     system      total        real
watir improved - click_no_wait   0.015000   0.047000   0.062000 (  9.308532)
=end

b.close
{noformat}

*watir/core.rb:*
{noformat}
require 'watir/win32ole'

require 'logger'
require 'watir/exceptions'
require 'watir/matches'

require 'watir/core_ext'
require 'watir/logger'
require 'watir/container'
require 'watir/locator'
require 'watir/page-container'
require 'watir/ie-class'
require 'watir/element'
require 'watir/element_collections'
require 'watir/form'
require 'watir/non_control_elements'
require 'watir/input_elements'
require 'watir/table'
require 'watir/image'
require 'watir/link'
require 'watir/html_element'

require 'watir/waiter'
require 'watir/module'
{noformat}

As it's possible to see from the results then i got click_no_wait to perform faster about 2-3 times (of course this depends on the PC and so on, but these are the results from my machine, which is a quite modest laptop)!

I call this as a quite big improvement if we think about all the Web 2.0 websites where #click_no_wait is often used.

Results:

1) removed all this unnecessary and funny-looking code which also had slight of a "criminal overengineering" smell on it (http://coderoom.wordpress.com/2010/06/23/criminal-overengineering/)

2) it is now easy debuggable, just use this monkey-patch:
{noformat}
Watir::PageContainer.class_eval {def spawned_click_no_wait_command(command) "ruby -e #{command.inspect}" end}
{noformat}

Maybe make it even more easier by using $DEBUG and if-statement or something similar?

3) requiring rubygems always, just in case, in the spawned process

4) click_no_wait performs 2-3x faster! It might be possible to improve the performance even more by doing some larger changes with the require statements in watir/core.rb - unfortunately it is not possible anymore to make changes in smaller steps

5) all click_no_wait invocations in unit tests use now development libraries


added urls for fix commits

> making Element#click_no_wait faster and easier debuggable
> ---------------------------------------------------------
>
>                 Key: WTR-449
>                 URL: http://jira.openqa.org/browse/WTR-449
>             Project: Watir
>          Issue Type: Improvement
>          Components: HTML Controls
>    Affects Versions: 1.6.5
>         Environment: all environments
>            Reporter: Jarmo Pertman
>            Priority: Major
>
> I have been thinking of improving Element#click_no_wait for some time now. I can see the following problems with current solution:
> 1) code is not good - method _code_that_copies_readonly_array with funny comment in main namespace, eval_in_spawned_process with TODO and not-TODO comments
> 2) users have a lot of problems with click_no_wait (http://bit.ly/asirep) and current code doesn't allow easy debugging with monkey-patching or similar solutions
> 3) in the spawned process used for click_no_wait, no rubygems is loaded thus it's possible that users without having RUBYOPT set to -rubygems can't use click_no_wait and since it's hard to debug (see point #2) then they don't even know what's the problem
> 4) click_no_wait is just slow because it loads too many files/libraries in it's own process - it should just load all the necessary things and nothing else
> 5) currently, in the unit tests click_no_wait process doesn't use development libraries
> Here is a benchmark:
> *click_no_wait_bench.rb:*
> {noformat}
> require "rubygems"
> require "watir"
> require "benchmark"
> b = Watir::Browser.new
> b.goto "http://www.google.com"
> times = 5
> # almost original watir 1.6.5 code
> # * added gsub to avoid bug for some ruby versions
> # * using ruby instead of start rubyw to measure time
> module Watir
>   module PageContainer
>     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 = "$HIDE_IE = #{$HIDE_IE};" # This prevents attaching to a window from setting it visible. However modal dialogs cannot be attached to when not visible.
>       ruby_code << "pc = #{attach_command}; " # pc = page container
>       # IDEA: consider changing this to not use instance_eval (it makes the code hard to understand)
>       ruby_code << "pc.instance_eval(#{command.inspect})"
>       exec_string = "ruby -e #{(load_path_code + '; ' + ruby_code).inspect}".gsub("\\\"", "'")
>       system(exec_string)
>     end
>   end
> end
> Benchmark.bmbm do |x|
>   x.report("watir 1.6.5") {times.times {b.button(:name => "btnG").click_no_wait}}
> end
> =begin
> Rehearsal -----------------------------------------------
> watir 1.6.5   0.031000   0.015000   0.046000 ( 26.136495)
> -------------------------------------- total: 0.046000sec
>                   user     system      total        real
> watir 1.6.5   0.000000   0.000000   0.000000 ( 23.600350)
> =end
> # improved section
> # * simplified click_no_wait methods
> # * requiring rubygems incase of missing RUBYOPT
> # * extracted ruby_execute_command into method for easier monkey-patching for debugging and testing reasons
> module Watir
>   class Element
>     def click_no_wait
>       assert_enabled
>       highlight(:set)
>       element = "#{self.class}.new(#{@page_container.attach_command}, :unique_number, #{self.unique_number})"
>       @page_container.click_no_wait(element)
>       highlight(:clear)
>     end
>   end
>   module PageContainer
>     def click_no_wait(element)
>       ruby_code = "require 'rubygems';" <<
>               "require 'watir/ie';" <<
>               "#{element}.click!"
>       system(spawned_click_no_wait_command(ruby_code))
>     end
>     def spawned_click_no_wait_command(command)
>       "start rubyw -e #{command.inspect}"
>     end
>     private :spawned_click_no_wait_command
>   end
> end
> Benchmark.bmbm do |x|
>   x.report("watir improved - ie") {times.times {b.button(:name => "btnG").click_no_wait}}
> end
> =begin
> Rehearsal -------------------------------------------------------
> watir improved - ie   0.032000   0.047000   0.079000 ( 20.141152)
> ---------------------------------------------- total: 0.079000sec
>                           user     system      total        real
> watir improved - ie   0.047000   0.016000   0.063000 ( 18.931083)
> =end
> # * loading watir/core.rb to reduce loading of unneeded files/libraries
> module Watir
>   class Element
>     def click_no_wait
>       assert_enabled
>       highlight(:set)
>       element = "#{self.class}.new(#{@page_container.attach_command}, :unique_number, #{self.unique_number})"
>       @page_container.click_no_wait(element)
>       highlight(:clear)
>     end
>   end
>   module PageContainer
>     def click_no_wait(element)
>       ruby_code = "require 'rubygems';" <<
>               "require 'watir/core';" <<
>               "#{element}.click!"
>       system(spawned_click_no_wait_command(ruby_code))
>     end
>     def spawned_click_no_wait_command(command)
>       "start rubyw -e #{command.inspect}"
>     end
>     private :spawned_click_no_wait_command
>   end
> end
> Benchmark.bmbm do |x|
>   x.report("watir improved - click_no_wait") {times.times {b.button(:name => "btnG").click_no_wait}}
> end
> =begin
> Rehearsal ------------------------------------------------------------------
> watir improved - click_no_wait   0.062000   0.015000   0.077000 (  9.729557)
> --------------------------------------------------------- total: 0.077000sec
>                                      user     system      total        real
> watir improved - click_no_wait   0.015000   0.047000   0.062000 (  9.308532)
> =end
> b.close
> {noformat}
> *watir/core.rb:*
> {noformat}
> require 'watir/win32ole'
> require 'logger'
> require 'watir/exceptions'
> require 'watir/matches'
> require 'watir/core_ext'
> require 'watir/logger'
> require 'watir/container'
> require 'watir/locator'
> require 'watir/page-container'
> require 'watir/ie-class'
> require 'watir/element'
> require 'watir/element_collections'
> require 'watir/form'
> require 'watir/non_control_elements'
> require 'watir/input_elements'
> require 'watir/table'
> require 'watir/image'
> require 'watir/link'
> require 'watir/html_element'
> require 'watir/waiter'
> require 'watir/module'
> {noformat}
> As it's possible to see from the results then i got click_no_wait to perform faster about 2-3 times (of course this depends on the PC and so on, but these are the results from my machine, which is a quite modest laptop)!
> I call this as a quite big improvement if we think about all the Web 2.0 websites where #click_no_wait is often used.
> Results:
> 1) removed all this unnecessary and funny-looking code which also had slight of a "criminal overengineering" smell on it (http://coderoom.wordpress.com/2010/06/23/criminal-overengineering/)
> 2) it is now easy debuggable, just use this monkey-patch:
> {noformat}
> Watir::PageContainer.class_eval {def spawned_click_no_wait_command(command) "ruby -e #{command.inspect}" end}
> {noformat}
> Maybe make it even more easier by using $DEBUG and if-statement or something similar?
> 3) requiring rubygems always, just in case, in the spawned process
> 4) click_no_wait performs 2-3x faster! It might be possible to improve the performance even more by doing some larger changes with the require statements in watir/core.rb - unfortunately it is not possible anymore to make changes in smaller steps
> 5) all click_no_wait invocations in unit tests use now development libraries
> Fixes are within commits:
> http://github.com/jarmo/watir/commit/7333081e30568ec73eb63f9f75562c2078c55481
> http://github.com/jarmo/watir/commit/c71cee1ac820493372d299b8f3a258b497b22e66

-- 
This message is automatically generated by JIRA.
-
If you think it was sent incorrectly contact one of the administrators: http://jira.openqa.org/secure/Administrators.jspa
-
For more information on JIRA, see: http://www.atlassian.com/software/jira

        


More information about the Wtr-development mailing list