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