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]

Attachment: watir_example.rb
Description: Binary data

Reply via email to