Skip to content

Simplifying tests for Gxt and Gwt applications

December 11, 2012

I have simplified the creation of widgets for the PageObject gem using the module shown below.  When testing with Watir or Selenium and Cheezy’s PageObject gem, this module allows for the easy creation of test widgets, primarily used for interacting with Gwt or Gxt web applications.

For example, a Gxt table consists of multiple div elements, and each row is itself a table element containing a single row.  To interact with the Gxt table, I extend the PageObject::Element::Table class:

class GxtTable < PageObject::Elements::Table

  @protected
  def child_xpath
    ".//descendant::tr"
  end
end

I then register the class with the PageObject gem. The register_widget method accepts a tag which will be used as the accessor, the class which will be added to the Elements module, and a html element tag which will be used as the top-level html element of the widget, or the html element used in the watir or selenium search.

Widgets.register_widget :gxt_table, GxtTable, :div

The GxtTable then behaves as if it were an html element for the purpose of page-object definitions.

class WidgetTestPageObject
  include PageObject

  gxt_table(:a_table, :id => "top_div_id")
  gxt_table :gxt_block_table do |element|
    "block_gxt_table"
  end

  div(:outer_div)
  gxt_table(:a_nested_gxt_table) {|page| page.outer_div_element.gxt_table_element}
end

The code for the WidgetModule is shown below. My next step is to submit this code as a pull request within PageObject.

require 'page-object/elements'
require 'page-object/platforms/selenium_webdriver/page_object'
require 'page-object/platforms/watir_webdriver/page_object'

module Widgets

  def self.register_widget(widget_tag, widget_class, base_element_tag)
    if widget_class.ancestors.include? PageObject::Elements::Element
      define_accessors(PageObject::Accessors, widget_tag)
      define_nested_elements(PageObject::Elements::Element, widget_tag)
      define_locators(PageObject, widget_tag)
      define_selenium_accessors(PageObject::Platforms::SeleniumWebDriver::PageObject, widget_tag, widget_class, base_element_tag)
      define_watir_accessors(PageObject::Platforms::WatirWebDriver::PageObject, widget_tag, widget_class, base_element_tag)
    end
  end

  @private

  def self.define_accessors(base, widget_tag)
    accessors_module = Module.new do
      class_eval "def #{widget_tag}(name, identifier={}, &block)
          identifier={:index=>0} if identifier.empty?
          define_method(\"\#{name}_element\") do
            return call_block(&block) if block_given?
            platform.#{widget_tag}_for(identifier.clone)
          end
          define_method(\"\#{name}?\") do
            return call_block(&block).exists? if block_given?
            platform.#{widget_tag}_for(identifier.clone).exists?
          end
        end"
    end

    base.send(:include, accessors_module)
  end

  def self.define_watir_accessors(base, widget_tag, widget_class, base_element_tag)
    base.send(:define_method, "#{widget_tag}_for") do |identifier|
      find_watir_element("#{base_element_tag}(identifier)", widget_class, identifier, base_element_tag)
    end

    base.send(:define_method, "#{widget_tag}s_for") do |identifier|
      find_watir_elements("#{base_element_tag}(identifier)", widget_class, identifier, base_element_tag)
    end

  end

  def self.define_selenium_accessors(base, widget_tag, widget_class, base_element_tag)
    base.send(:define_method, "#{widget_tag}_for") do |identifier|
      find_selenium_element(identifier, widget_class, base_element_tag)
    end

    base.send(:define_method, "#{widget_tag}s_for") do |identifier|
      find_selenium_elements(identifier, widget_class, base_element_tag)
    end
  end

  def self.define_nested_elements(base, widget_tag)
    base.send(:define_method, "#{widget_tag}_element") do |identifier={}|
      identifier={:index => 0} if identifier.empty?
      @platform.send("#{widget_tag}_for", identifier.clone)
    end

    base.send(:define_method, "#{widget_tag}_elements") do |identifier={}|
      identifier={:index => 0} if identifier.empty?
      @platform.send("#{widget_tag}s_for", identifier.clone)
    end
  end


  def self.define_locators(base, widget_tag)
    base.send(:define_method, "#{widget_tag}_element") do |identifier={}|
      identifier={:index => 0} if identifier.empty?
      platform.send("#{widget_tag}_for", identifier.clone)
    end

    base.send(:define_method, "#{widget_tag}_elements") do |identifier={}|
      platform.send("#{widget_tag}s_for", identifier.clone)
    end
  end
end
2 Comments
  1. bgoadrn's avatar

    I’d be interested in seeing actually real-world implementation of this!

    • William J Powell's avatar

      My fork of PageObject on GitHub has all the tests for this module including a cucumber feature which uses the GxtTable shown to test a gxt examples page. Is there a particular example that would help?

Leave a reply to wjpowell Cancel reply