One thing I have noticed with my upgrade to 1.9.3/4.0.0 is that execution 
time is so fast that I have to put extra waits in my code to cope. Don't 
know if this is a good or bad thing but I have spent a lot of time making 
the framework robust since the upgrade.

On Tuesday, 9 October 2012 10:24:14 UTC+13, Paul wrote:
>
> The app under test is a complicated and dynamic health care management 
> system with many sub applications. I will use a handle on an element when I 
> know that the underlying structure will not change in the scope of a test 
> case but it is best to use a 'fresh' handle otherwise. Execution time is 
> not affected, 99% of execution time is in the browser anyway. I do notice a 
> significant performance improvement from my recent upgrade, however, but I 
> was stuck on 1.8.7/3.0.0 for a long time.
>
> On Tuesday, 9 October 2012 04:21:50 UTC+13, Jarmo Pertman wrote:
>>
>> Hi!
>>
>> Thanks for sharing, maybe someone finds it helpful :)
>>
>> I have a question though - why do you need to "climb up" in the DOM to 
>> get the latest elements? Are you seeing some errors if you don't do that? 
>> Is Watir caching too much for you?
>>
>> Jarmo Pertman
>> -----
>> IT does really matter - http://itreallymatters.net
>>
>>
>> On Monday, October 8, 2012 3:36:19 AM UTC+3, Paul wrote:
>>>
>>> This is not a question but a share of my latest Watir framework engine 
>>> code using metaprogramming.
>>>
>>> In the app under test there are a lot of deeply nested frames and divs, 
>>> in addition there is lots of javascipt and Ajax going on that can change 
>>> page layouts often.
>>> Every call to a Watir method should step up the DOM stack (actually a 
>>> tree but I will logically call it a stack) to Browser to ensure we have the 
>>> latest handles on all elements.
>>>
>>> For example:
>>>   def context__frame
>>>     @browser.frame(:name, 'Context')
>>>   end
>>>   def application__frame
>>>     context__frame.frame(:name, 'Application')
>>>   end
>>>   def main__frame
>>>     application__frame.frame(:name, 'Main')
>>>   end
>>>   def main__div
>>>     main__frame.div(:id, 'Main')
>>>   end
>>>
>>> A call to main__div() calls main_frame() that calls application__frame() 
>>> that calls context__frame().
>>>
>>> All good, but I should extract out the watir how, what info to a single 
>>> source. I created hashes of the how whats:
>>>
>>>     @elements = {
>>>       :context__frame       => {:how => :name, :what => 'Context'},
>>>       :application__frame   => {:how => :name, :what => 'Application'},
>>>       :main__frame          => {:how => :name, :what => 'Main'},
>>>       :main__div            => {:how => :id, :what => 'Main'},
>>>     }
>>>
>>> Now the methods look like this:
>>>   def context__frame
>>>     field = @elements[:context__frame]
>>>     @browser.frame field[:how], field[:what]
>>>   end
>>>   def application__frame
>>>     field = @elements[:application__frame]
>>>     context__frame.frame field[:how], field[:what]
>>>   end
>>>   def main__frame
>>>     field = @elements[:main__frame]
>>>     application__frame.frame field[:how], field[:what]
>>>   end
>>>   def main__div
>>>     field = @elements[:main__div]
>>>     main__frame.div field[:how], field[:what]
>>>   end
>>>
>>> The concept here is that each method up the stack represents the 
>>> container of the caller.
>>> Note the method names are the same as the hash keys.
>>> This is a trivial example, but you get the picture, I ended up with 50 
>>> or so methods that look essentially the same, and more would be added all 
>>> the time.
>>>
>>> Bring on method_missing and ghost methods. All I needed to do is add the 
>>> container of each element to my hash (@elements in this case). The hash now 
>>> looks like this:
>>>
>>>     @elements = {
>>>       :context__frame       => {:how => :name, :what => 'Context'},
>>>       :application__frame   => {:who => :context__frame, :how => :name, 
>>> :what => 'Application'},
>>>       :main__frame          => {:who => :application__frame, :how => 
>>> :name, :what => 'Main'},
>>>       :main__div            => {:who => :main__frame, :how => :id, :what 
>>> => 'Main'},
>>>     }
>>>
>>> Now I have the who as well as the how and what.
>>> Next I wrote the method_missing and the handler method:
>>>
>>>   def method_missing(method, *args, &block)
>>>     if method.to_s =~ /__(\w+)$/
>>>       get_field_how_what($1.to_sym, method.to_sym)
>>>     else
>>>       super # let Ruby continue with method lookup
>>>     end
>>>   end
>>>  
>>>   def get_field_how_what(watir_method, method_key)
>>>     container = @elements[method_key][:who]
>>>     how = @elements[method_key][:how]
>>>     what = @elements[method_key][:what]
>>>  
>>>     # if the method exists - call it
>>>     if self.respond_to? container
>>>       # method exists
>>>       element = method(container).call
>>>     else
>>>       # method not exist
>>>       element = self.send :method_missing, container
>>>     end
>>>     # parent element exists - call the watir method on it
>>>     element.method(watir_method).call how, what
>>>   end
>>>
>>> Then I deleted all the methods (in this example main__div(), 
>>> main_frame() and application__frame()) but left context__frame() as it is 
>>> the top level method that uses @browser.
>>>
>>> This works by using 'ghost methods', methods that do not exist until you 
>>> define them at runtime.
>>> In method_missing() I catch any missing method that ends with __frame, 
>>> __div, __button, etc, and pass these off to get_field_how_what().
>>> I know the watir method to call from the regex match on the missing 
>>> method name. 
>>>
>>> Using the method name I can access the global hash of all the fields 
>>> (@elements) and know what the parent container is (:who) and how to access 
>>> the element (:how, :what).
>>> If the parent container method exists, I call it, returning the parent 
>>> element.
>>> If the container method does not exist, I call method_missing again with 
>>> the container (:who). 
>>> This gets done repeatedly until we get to the top level method, then 
>>> each parent is returned, effectively moving back down the stack to the 
>>> element we want.
>>>
>>> I have created an example that hits the Ruby web site and accesses some 
>>> elements:
>>>
>>> require 'rubygems'
>>> require 'watir-classic'
>>>
>>> class Watir_example
>>>   def initialize
>>>     @elements = {
>>>       :page__div            => {:how => :id, :what => 'page'},
>>>       :main_wrapper__div    => {:who => :page__div, :how => :id, :what 
>>> => 'main-wrapper'},
>>>       :main__div            => {:who => :main_wrapper__div, :how => :id, 
>>> :what => 'main'},
>>>       :content_wrapper__div => {:who => :main__div, :how => :id, :what 
>>> => 'content-wrapper'},
>>>       :head_wrapper_1__div  => {:who => :content_wrapper__div, :how => 
>>> :id, :what => 'head-wrapper-1'},
>>>       :head_wrapper_2__div  => {:who => :head_wrapper_1__div, :how => 
>>> :id, :what => 'head-wrapper-2'},
>>>       :head__div            => {:who => :head_wrapper_2__div, :how => 
>>> :id, :what => 'head'},
>>>       :intro__div            => {:who => :head__div, :how => :id, :what 
>>> => 'intro'},
>>>     }
>>>   
>>>     @browser = Watir::Browser.new
>>>     @browser.goto 'http://www.ruby-lang.org/en/'
>>>   end
>>>   
>>>   def page__div
>>>     field = @elements[:page__div]
>>>     @browser.div(field[:how], field[:what])
>>>   end
>>>   
>>>   def method_missing(method, *args, &block)
>>>     if method.to_s =~ /__(\w+)$/
>>>       get_field_how_what($1.to_sym, method.to_sym)
>>>     else
>>>       super # let Ruby continue with method lookup
>>>     end
>>>   end
>>>   
>>>   def get_field_how_what(watir_method, method_key)
>>>     container = @elements[method_key][:who]
>>>     how = @elements[method_key][:how]
>>>     what = @elements[method_key][:what]
>>>   
>>>     puts "get_field_how_what: #{method_key}"
>>>     puts "container: #{container}"
>>>     # if the method exists - call it
>>>     if self.respond_to? container
>>>       # method exists
>>>       puts "  ->method(#{container}).call"
>>>       element = method(container).call
>>>     else
>>>       # method not exist
>>>       puts "  ->method_missing(#{container})"
>>>       element = self.send :method_missing, container
>>>     end
>>>     # parent element exists - call the watir method on it
>>>     puts "  ->#{element}.#{watir_method}(:#{how.to_s}, '#{what}')"
>>>     element.method(watir_method).call how, what
>>>   end
>>> end
>>>
>>> intro_div = Watir_example.new.intro__div
>>> puts intro_div.text
>>>
>>>
>>> I have attached a file of this as well. I am using Ruby 1.9.3 with 
>>> watir-classic 3.2.0, you may have to change the require to 'watir' if using 
>>> a previous version.
>>> I am also only testing IE, You can change to fire-watir or even 
>>> webdriver and should still work.
>>> Run and observe the output, it helps with understanding the concepts.
>>>
>>> - enjoy...
>>>
>>

-- 
Before posting, please read http://watir.com/support. In short: search before 
you ask, be nice.

[email protected]
http://groups.google.com/group/watir-general
[email protected]

Reply via email to