Class: PageFactory

Inherits:
Object
  • Object
show all
Defined in:
lib/test-factory/page_factory.rb

Overview

The PageFactory class provides a set of methods that allow the rapid creation of page element definitions–known colloquially as “page objects”. These elements are defined using Watir syntax. Please see www.watir.com if you are not familiar with Watir.

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(browser, visit = false) ⇒ PageFactory

As the PageFactory will be the superclass for all your page classes, having this initialize method here means it’s only written once.



24
25
26
27
28
29
# File 'lib/test-factory/page_factory.rb', line 24

def initialize browser, visit = false
  @browser = browser
  goto if visit
  expected_element if respond_to? :expected_element
  has_expected_title? if respond_to? :has_expected_title?
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(sym, *args, &block) ⇒ Object

Catches any “missing” methods and passes them to the browser object–which means that Watir will take care of parsing them, so the assumption is that the method being passed is a valid method for the browser object.



35
36
37
# File 'lib/test-factory/page_factory.rb', line 35

def method_missing sym, *args, &block
  @browser.send sym, *args, &block
end

Class Method Details

.button(button_text, *alias_name) ⇒ Object

Use this for buttons that are safe to define by their value attribute. This method will return two methods for interacting with the button: one that refers to the button itself, and one that clicks on it. Since it’s assumed that the most common thing done with a button is to click it, the method for clicking it will be named according to the value of the button, and the method for the button itself will have “_button” appended to it. Any special characters are stripped from the string. Capital letters are made lower case. And spaces and dashes are converted to underscores. The last parameter in the method is optional. Use it when you need the method name to be different from the text of the button–for example if the button text is unhelpful, like “Go”, or else it changes (e.g., from “Update” to “Edit”) and you don’t want to have to go through all your data objects and step definitions to update them to the new method name.

Examples:

button("Click Me For Fun!") => Creates the methods #click_me_for_fun and #click_me_for_fun_button
link("Click Me For Fun!", :click_me) => Creates the methods #click_me and #click_me_link

Parameters:

  • button_text (String)

    The contents of the button’s value tag in the HTML



146
147
148
# File 'lib/test-factory/page_factory.rb', line 146

def button button_text, *alias_name
  elementize(:button, button_text, *alias_name)
end

.element(name, &block) ⇒ Object Also known as: action, value, p_element, p_action, p_value

The basic building block for defining and interacting with elements on a web page. # Use in conjunction with Watir to define all elements on a given page that are important to validate.

Methods that take one or more parameters can be built with this as well.

Examples:

element(:title) { |b| b.text_field(:id=>"title-id") }
value(:page_header) { |b| b.h3(:class=>"page_header").text }
action(:continue) { |b| b.frm.button(:value=>"Continue").click } => Creates a #continue method that clicks the Continue button
p_element(:select_style) { |stylename, b| b.div(:text=>/#{Regexp.escape(stylename)}/).link(:text=>"Select").click } => #select_style(stylename)


84
85
86
87
88
89
# File 'lib/test-factory/page_factory.rb', line 84

def element name, &block
  raise "#{name} is being defined twice in #{self}!" if self.instance_methods.include?(name.to_sym)
  define_method name.to_s do |*thing|
    Proc.new(&block).call *thing, self
  end
end

.expected_element(element_name, timeout = 30) ⇒ Object

Define this in a page class and when that class is instantiated it will wait until that element appears on the page before continuing with the script.

Parameters:

  • element_name (Symbol)

    The method name of the element that must be present on the page



54
55
56
57
58
# File 'lib/test-factory/page_factory.rb', line 54

def expected_element element_name, timeout=30
  define_method 'expected_element' do
    self.send(element_name).wait_until_present timeout
  end
end

.expected_title(expected_title) ⇒ Object

Define this in a page class and when the class is instantiated it will verify that the browser’s title matches the expected title. If there isn’t a match, it raises an error and halts the script.

Parameters:

  • expected_title (String)

    The exact text that is expected to appear in the Browser title when the page loads



65
66
67
68
69
70
# File 'lib/test-factory/page_factory.rb', line 65

def expected_title expected_title
  define_method 'has_expected_title?' do
    has_expected_title = expected_title.kind_of?(Regexp) ? expected_title =~ @browser.title : expected_title == @browser.title
    raise "Expected title '#{expected_title}' instead of '#{@browser.title}'" unless has_expected_title
  end
end

.inherited(klass) ⇒ Object



175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'lib/test-factory/page_factory.rb', line 175

def inherited klass
  klass.instance_eval {

    # Creates a method, #wait_for_ajax, usable in your Page Classes, that executes
    # the 'jQuery.active' Javascript snippet each second until timeout.
    #
    # If timeout is exceeded, raises Watir::Wait::TimeoutError exception. The returned error
    # message is customizable.
    #
    define_method 'wait_for_ajax' do |timeout=10, message|
      timeout.times do
        sleep 0.3
        return true if @browser.execute_script('return jQuery.active').to_i == 0
        sleep 0.7
      end
      raise Watir::Wait::TimeoutError, "Ajax calls continued beyond #{timeout} seconds. #{message}"
    end

  }
end

Use this for links that are safe to define by their text string. This method will return two methods for interacting with the link: one that refers to the link itself, and one that clicks on it. Since it’s assumed that the most common thing done with a link is to click it, the method for clicking it will be named according to the text of the link, and the method for the link itself will have “_link” appended to it. Any special characters are stripped from the string. Capital letters are made lower case. And spaces and dashes are converted to underscores.

The last parameter in the method is optional. Use it when you need the method name to be different from the text of the link–for example if the link text is something unhelpful, like “here”, or else the link text gets updated (e.g., what was “Log In” is now “Sign In”, instead) and you don’t want to have to go through all your data objects and step definitions to update them to the new method name.

Examples:

link("Click Me For Fun!") => Creates the methods #click_me_for_fun and #click_me_for_fun_link
link("Click Me For Fun!", :click_me) => Creates the methods #click_me and #click_me_link


119
120
121
# File 'lib/test-factory/page_factory.rb', line 119

def link link_text, *alias_name
  elementize(:link, link_text, *alias_name)
end

.page_url(url) ⇒ Object

Define this in a page class and when you use the “visit” method to instantiate the class it will enter the URL in the browser’s address bar.



44
45
46
47
48
# File 'lib/test-factory/page_factory.rb', line 44

def page_url url
  define_method 'goto' do
    @browser.goto url
  end
end

.undefine(*methods) ⇒ Object

TestFactory doesn’t allow defining a method in a child class with the same name as one already defined in a parent class. The thinking here is: “Out of sight, out of mind.” Meaning: you or a team mate might not know or have forgotten that a given element is already defined in a parent class, and so define it again. TestFactory’s restriction is there to help prevent this.

However, in some cases you may have a child page class with a special circumstance, where the parent class’s version of the method really doesn’t apply, and you want to use the same method name in this child class because, really, no other method name would fit quite as well.

The #undefine method is for those rare cases. Note: If you start using this method a lot then you should consider that a sign that perhaps you’re putting too many method definitions into parent page classes.

Examples:

undefine :status, :doc_id => Undefines the specified methods in the current class


171
172
173
# File 'lib/test-factory/page_factory.rb', line 171

def undefine *methods
  methods.each{ |m| undef_method m }
end