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