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]
watir_example.rb
Description: Binary data
