One thing I have noticed with my upgrade to 1.9.3/4.0.0 is that execution time is so fast that I have to put extra waits in my code to cope. Don't know if this is a good or bad thing but I have spent a lot of time making the framework robust since the upgrade.
On Tuesday, 9 October 2012 10:24:14 UTC+13, Paul wrote: > > 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]
