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]
