Creating Custom Element Widgets to use with Page Object

First things first. Why?

JavaScript has made it easier for developers to create custom controls that go above and beyond the behavior that only a single html element can provide.

Libraries such jquery ui widgets and kendo ui offer an example of such controls.

Testing these controls can involve a few things:

  • Complex wait conditions
  • Knowledge of hidden html elements
  • Knowlegde of multiple html elements

This leads to duplication of this logic across the test suite where these elements are used. Using a widget allows you to encapsulate this behavior and reduce the amount of duplication.

Where to start?

All widgets need to include PageObject::Elements::Element in its ancestry.

Therefore a good place to start is deciding what html best represents the new control and then create a new class

class AutoComplete < PageObject::Elements::TextField

end

Registration

Adding the widget is straight forward. The three pieces of infomation you need are:

  • widget_tag => name to define widget accessor method as
  • widget_class => class where widget is defined
  • base_element_tag => tag of the element the widget inherits
PageObject.register_widget(:auto_complete, AutoComplete, 'text_Field')

Usage

class TestPage
	include PageObject

	auto_complete(:city, :id => 'City')

end

This will define two methods we can use with in our page,

city_element - this will return an instance of **PageObject::Elements::TextField**

city? - returns whether or not the element exists

**For a more indepth look at the standard accessor methods checkout accessor_blog.md

Adding Behaviour

Thus far, adding a new widget will not add much value.

Lets change that, here are the ways to add behaviour to the widget.

Accessor Methods

One way we can do this is by defining a class level method in our new elements class

class AutoComplete < PageObject::Elements::TextField

	def self.accessor_methods(accessor, name)

		accessor.send :define_method, "#{name}=" do |value|
	        #code for desired behaviour
	    end

	    accessor.send :define_method, "#{name}" do |value|
	        #code for desired behaviour
	    end

	    #...

	end

end

This will define the “#{name}=”, and “#{name}”.

Lets think meta

Calling other methods on the element, and interacting with the element is not as stright forward as normal since the methods are dynamically generated based on the element name.

So inorder to interact with the element we need to think meta.

def self.accessor_methods(accessor, name)

		accessor.send :define_method, "#{name}=" do |value|
			self.send("#{name}_element")
	    end

	end

In the above code example, the self.send(“#{name}_element”) is the interesting piece, because self in the context of the define_method block is actually the object in which the accessor method was called on.

Huh? #metaprogramming

All this means is that in the example below, self is an instance of TestPage.

class TestPage
	include PageObject

	auto_complete(:city, :id => 'City')

end

The other way

Sometimes using the accessor methods is not a viable option, because we need to locate the element dynamically in a table.

To add behavior to an instance of a class, simply add instance methods

class AutoComplete < PageObject::Elements::TextField

	def set(value)
	    #code for desired behaviour
	end

end

Gotchas

You should define dynamically created methods with name being part of the method name

ex. "#{name}_options"

Failing to do so will result in runtime errors when you attempt to define two of the same widget in a PageObject

Sharing behaviour between dynamically defined methods and instance methods can be done with class level methods.

Encapsulating private behaviour is not as easy as using the private section. You need to send the private message to the new accessor module with a list of methods to make private.

Example:

class AutoComplete < PageObject::Elements::TextField

	def self.accessor_methods(accessor, name)
	
		accessor.send :define_method, "#{name}=" do |value|
	        #code for desired behaviour
	    end

	    accessor.send :define_method, "#{name}" do |value|
	        #code for desired behaviour
	    end

	    #...

	    accessor.send :private, "#{name}", "#{name}="

	end
end